-
Notifications
You must be signed in to change notification settings - Fork 9
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
Proposal: bypass the AR association for more efficient DB queries #90
base: main
Are you sure you want to change the base?
Conversation
else | ||
PropertySets::Casting.read(type, value) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic has been moved into PropertySets::ValueReader
, but I haven't changed it.
# | ||
def lookup_value(_type, name) | ||
_lookup_value(name) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an example of things that I've left in the code just to make it easier to follow the changes, and that should be cleaned up before this can move forward.
@@ -263,6 +263,8 @@ class AnotherThing < ActiveRecord::Base | |||
{ :id => account.texts.lookup(:bar).id, :name => "bar", :value => "1" } | |||
]) | |||
|
|||
account.reload | |||
|
|||
expect(account.texts.foo).to eq("0") | |||
expect(account.texts.bar).to eq("1") | |||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These were the only changes needed in the test files. I couldn't find a way around adding #to_a
. I'm not a big fan of how an AR:Relation can automatically be reified to behave like an Enumerable when required, so I didn't try to implement it in the new AssociationWrapper
.
On the #reload
, this is necessary because this test uses account.update_attributes!(:texts_attributes => [...])
, which bypasses the property_set
gem and goes directly to ActiveRecord. I couldn't find a convenient hook to invalidate the cache automatically.
Compare this to the next test below (it "be updateable as a nested structure"
), that instead uses account.update_attributes!(:settings => { ... })
. That is intercepted by the property_set
gem, and technically there the #reload
is not necessary (even though the test was calling it 🤷 ).
This started from my observation that accessing one single property from a "property set" loads all entries from the DB [1], and that updating one will check if all the others already exist [2] because of a uniqueness validation (?).
I saw that something similar was attempted before (#79), but that was rejected because (if I follow correctly) it would modify the behaviour of the
has_many
association in an inconsistent way. I think that there is some merit in that line of reasoning.For that reason I tried a different approach. I based it on the idea that a
has_many
ActiveRecord association is not the best tool to manage this sort of data. It comes with too much baggage, and its interface provides too many ways to interact with the data. Keeping the property_set entries in the DB as individual records might be ok, but I thought that they could be accessed directly without going through thehas_many
association.Summary of Contents
account.settings.last_login
will only SQL-SELECT that one record instead of everything. And that code likeaccount.settings.save
will only SQL-UPDATE the records that actually changed, rather than loading and validating them all.AssociationWrapper
class. Most of the new logic is in there, and I've modified the existing code with what I needed to inject the new wrapper.has_many
) and my new wrapper. The two alternative implementations are kept sepatate for now because it makes it easier to review the changes (they're mostly isolated in their own file), and because it would make it safer to roll this out (feature flags). I am not suggesting that the code in this PR should be final.@ivars
) of the AR associations.has_many
should be easier to cache.[1]: e.g.
SELECT * from account_settings WHERE account_id = ?
[2] e.g.
SELECT 1 AS one FROM account_settings WHERE account_settings.name = ? AND (account_settings.id != ?) and account_settings.account_id = ?
, for each record that is part of the property_set.