-
Notifications
You must be signed in to change notification settings - Fork 72
THREESCALE-12434: Migrate from protected attributes to strong parameters - Part 3 #4255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: strong-params-part2
Are you sure you want to change the base?
Changes from 8 commits
ed4042e
cbd6745
dd28b24
953b148
cb88c1f
3909d03
60d4041
baa3c94
82b56df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,11 @@ module ServiceExtension | |
| end, class_name: 'Metric' | ||
| end | ||
|
|
||
| # This class is a compatibility layer added in https://github.com/3scale/porta/pull/1211 | ||
| # to make the legacy code work after BackendApis ("API as a Product" feature) were introduced. | ||
| # For example, code like `proxy.update(api_backend: "value")` still works, even though `api_backend` is | ||
| # now a field of BackendApi (aka `private_endpoint`), and not Proxy's. | ||
| # It is achieved by building a BackendApi and a BackendApiConfig objects lazily . | ||
| class BackendApiProxy | ||
| attr_reader :service | ||
| delegate :account, :backend_apis, :backend_api_configs, to: :service | ||
|
|
@@ -32,8 +37,16 @@ def backend_api_config | |
| backend_api_configs.build(path: '/', backend_api: backend_api) | ||
| end | ||
|
|
||
| # Lazy building is required for some old code to work. | ||
| # However, it is needed to be dissociated, because otherwise `provider.backend_apis` will include this object, | ||
| # and validation will fail, because the lazily built BackendApi object itself might not be valid (due to a missing | ||
| # `backend_endpoint`). | ||
| def backend_api | ||
|
akostadinov marked this conversation as resolved.
|
||
| @backend_api ||= backend_api_configs.first&.backend_api || account.backend_apis.build(system_name: service_system_name, name: "#{service_name} Backend", description: "Backend of #{service_name}") | ||
| # Return early if we already have a persisted backend_api | ||
| return @backend_api if @backend_api&.persisted? | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This issue with This commit contains a solution that Claude provided, and I'm putting his explanation below. I am not convinced or happy with this though, and I find the whole lazy building messy... I think we need to review the behavior again, and try to get rid of any lazy building, because it just makes things complicated and error-prone.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BackendApiProxy Cache Issue After Removing protected_attributes_continuedProblemAfter removing the
Root CauseThe behavioral difference is caused by How BackendApiProxy caches a backend_apiWhen a Service is created,
@backend_api ||= backend_api_configs.first&.backend_api ||
account.backend_apis.build(system_name: ..., name: ..., description: ...)Since no What changed: inverse_of behaviorThe Proxy model has On master (with protected_attributes_continued)The gem subtly breaks This means the stale On the branch (without the gem)
Later calls to EvidenceTracing Master: Branch: Impact on Production CodeThe main production code path that uses def save!(params)
@service.save! # triggers create_default_proxy -> caches stale @backend_api_proxy
...
save_default_backend_api(params) # calls backend_api_proxy.update!(params)
end
Other production code paths access backend_api through FixCode fix:
|
||
|
|
||
| # Try to get backend_api from backend_api_configs, or keep the memoized unpersisted one, or build a new one | ||
| @backend_api = backend_api_configs.first&.backend_api || @backend_api || build_dissociated_backend_api | ||
| end | ||
|
|
||
| def update!(attrs = {}) | ||
|
|
@@ -52,6 +65,18 @@ def update(attrs = {}) | |
| rescue ActiveRecord::RecordInvalid | ||
| false | ||
| end | ||
|
|
||
| private | ||
|
|
||
| # Build a BackendApi without adding it to `backend_apis` association to avoid polluting it with unpersisted records. | ||
| def build_dissociated_backend_api | ||
| BackendApi.new( | ||
| account: account, | ||
| system_name: service_system_name, | ||
| name: "#{service_name} Backend", | ||
| description: "Backend of #{service_name}" | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def backend_api_proxy | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -178,8 +178,7 @@ def validate_expiration_date | |||||||
| end | ||||||||
|
|
||||||||
| def generate_if_missing | ||||||||
| return if persisted? | ||||||||
| return if @plaintext_value.present? | ||||||||
| return if value.present? | ||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was required due to the removal of the The part that got broken is: porta/test/integration/user-management-api/base_controller_test.rb Lines 133 to 135 in 0264807
and, as a result, the tests that were relying on this So, apparently, the gem was overriding the After removing the gem, the execution stack consists of the standard I think checking for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jlledom can you please take a look at this change and let me know if you think it's fine? All tests are passing 🙌
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been checking and this looks correct to me. Even better than before. |
||||||||
|
|
||||||||
| @plaintext_value = self.class.random_id | ||||||||
| self.value = self.class.compute_digest(@plaintext_value) | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,10 +32,8 @@ def diff(other) | |
| end | ||
|
|
||
| def revert_attributes | ||
| accessible = template.class.accessible_attributes.delete('draft').add('updated_at').add('updated_by') | ||
|
|
||
| accessible << state.to_s | ||
| attributes.slice *accessible | ||
| accessible = [:updated_at, :updated_by, state.to_s] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't remember how exactly I came up with this replacement, but I think it was just debugging and checking what fields were actually available there.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to Claude, I broke this method in this commit: 33b70d3 However, after my changes it was effectively doing the same you do in this commit, just returning So I guess:
There are no tests for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm... I don't see changes to that file in the commit you mentioned 🤔
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that commit, I removed the protected attributes for the - attr_accessible :provider, :draft, :liquid_enabled, :handlerAfter that change,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I, I see, right, thank you for the explanation! Yeah, it rings a bell now, indeed, that
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think the versioning feature is working fine? didn't I break it?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I tried, it seemed to be doing the right thing.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A good engineer knows how to make mistakes!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make a mistake that only manifests after another engineer makes a valid change 💪 |
||
| attributes.slice(*accessible) | ||
|
jlledom marked this conversation as resolved.
Outdated
|
||
| end | ||
|
|
||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,14 +10,15 @@ def self.fetch_with_retry!(options, &block) | |
| retries = 0 | ||
|
|
||
| begin | ||
| MailDispatchRule.find_or_create_by!(options, &block) | ||
| # Use find_by + create! directly to avoid transaction wrapper from create_or_find_by! | ||
| # This preserves the old behavior from protected_attributes_continued gem | ||
| # where records created in the block are committed even if the outer create fails | ||
| find_by(options) || create!(options, &block) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure whether this is the right thing to do... This is basically keeping the old behavior. The test that is failing without it is: It verifies (how I understand it) that if a race condition occurs when creating With no code change, the test fails with the error: So, with With the gem removed, the ActiveRecord method is being called directly: create_or_find_by! wraps the createion in a transaction, so if anything fails within it, all created objects get rolled back.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, honestly, my feeling is that it's that the test doesn't represent what would be happening in the production code. If the object is created in two different places, then Rails' standard So, we probably don't need this loop, and the race condition simulation should be done in a different way. However, I don't want to risk it, because I don't quite understand the possible use case, so I decided to just keep the previous behavior.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I don't remember all details now, but I think any changes there will not have any effect.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found this: https://redhat.atlassian.net/browse/THREESCALE-11825
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good, thank you for the info!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove the class then? No need for a migration, we can add the migration in the JIRA. But why keep this code at all?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I prefer not to make changes that are not directly related to the purpose of the PR... There is an issue for removing MailDispatchRule, so I'd prefer to remove the class as part of that effort. |
||
| rescue ActiveRecord::RecordNotUnique | ||
| if retries > 10 | ||
| raise | ||
| else | ||
| retries += 1 | ||
| retry | ||
| end | ||
| raise if retries > 10 | ||
|
|
||
| retries += 1 | ||
| retry | ||
| end | ||
| end | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.