Skip to content

Set inverse asoociation for cache_has_many without :embed => true - #360

Closed
roccoblues wants to merge 14 commits into
Shopify:masterfrom
roccoblues:add_inverse_association
Closed

roccoblues wants to merge 14 commits into
Shopify:masterfrom
roccoblues:add_inverse_association

Conversation

@roccoblues

Copy link
Copy Markdown

Fixes #357

Comment thread CHANGELOG.md Outdated

#### 1.0.0 Unreleased

- Set inverse asoociation for cache_has_many without `:embed => true`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asoociation -> association :P

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say it is for an "id embedded cach_has_many associations" rather than saying what it isn't for.

@dylanahsmith dylanahsmith left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like prefetch_one_association also needs to be changed so that the inverse cached association is also set when the association is batch loaded (e.g. through using the :includes option on fetch).

Comment thread lib/identity_cache/configuration_dsl.rb Outdated
options[:records_variable_name] = "cached_#{association}"
options[:prepopulate_method_name] = "prepopulate_fetched_#{association}"

inverse_name = options[:association_reflection].inverse_of.try(:name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer try! over try, since the latter also does a respond_to? check that can hide bugs.

Comment thread lib/identity_cache/configuration_dsl.rb Outdated
@#{options[:records_variable_name]} ||= #{options[:association_reflection].class_name}.fetch_multi(#{options[:cached_ids_name]})
@#{options[:records_variable_name]} ||= begin
#{options[:association_reflection].class_name}.fetch_multi(#{options[:cached_ids_name]}).map do |record|
record.send('#{inverse_name}=', self) if #{inverse_name.present?}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to set the association itself, since putting records loaded from the cache into an active record association would make it easier to unintentionally persist that data to the database, which could persist cache corruption. Instead, we want to make sure they use the cached accessor to access it. Can you re-use set_inverse_of_cached_association to do it the same way as embedded cached associations?

Comment thread test/inverse_of_test.rb Outdated
end

def test_fetch_should_setup_association_for_cache_has_many
Item.cache_has_many :associated_records

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly use the :embed => :ids option for clarity

Comment thread test/inverse_of_test.rb Outdated

records = item.fetch_associated_records
records.each do |r|
assert_equal item.object_id, r.item.object_id

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should need to use r.fetch_item.

Comment thread test/inverse_of_test.rb Outdated

item = Item.find_by_id(@item.id)

records = item.fetch_associated_records

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always just delegate to the active record association, since item wasn't fetched from the cache.

Comment thread test/inverse_of_test.rb Outdated
@@ -0,0 +1,53 @@
require "test_helper"

class InverseOfTest < IdentityCache::TestCase

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't there already tests for setting the inverse cached association? If there are already tests in denormalized_has_many_test.rb, denormalized_has_one_test.rb and normalized_belongs_to_test.rb then we just need tests for this behaviour in normalized_has_many_test.rb

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread test/inverse_of_test.rb Outdated
item = Item.find_by_id(@item.id)

record = item.fetch_associated
assert_equal item.object_id, record.item.object_id

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always just delegate to the active record association, since item wasn't fetched from the cache and this is an embedded cached association.

Comment thread test/inverse_of_test.rb Outdated
end

def test_fetch_should_setup_association_for_cache_belongs_to
Item.cache_belongs_to :associated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it should raise, since the model doesn't even have a belongs_to :associated association, nor does the table have an associated_id column on the items table. I think this must be actually using the has_one association.

Comment thread CHANGELOG.md Outdated

#### 1.0.0 Unreleased

- Set inverse asoociation for cache_has_many without `:embed => true`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say it is for an "id embedded cach_has_many associations" rather than saying what it isn't for.

Comment thread test/inverse_of_test.rb Outdated
def test_fetch_should_setup_association_for_cache_has_many
Item.cache_has_many :associated_records

item = Item.find_by_id(@item.id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use find when you always expect it to return a record.

@roccoblues

Copy link
Copy Markdown
Author

@dylanahsmith I have the feeling we've been talking about different things. Can you have a look at the code in the related issue again? Here's what I meant in pseudo-code:

item = Item.find(ID) # fetches item from the db
associated_records = item.fetch_associated # fetches from cache

# now I wanted the item accessor on the records fetched from cache
# to *not* load the item again
associated_records.first.item.object_id == item.object_id

I just found that you even have a test that this should explicitly not happen: https://github.com/Shopify/identity_cache/blob/master/test/denormalized_has_many_test.rb#L124 ?

Did you instead think about this?

item = Item.fetch(ID) # fetches item from the cache
associated_records = item.fetch_associated # fetches from cache

# the fetch_ method should return the already fetch item from cache?
associated_records.first.fetch_item.object_id == item.object_id

@dylanahsmith

Copy link
Copy Markdown
Contributor

I just found that you even have a test that this should explicitly not happen: https://github.com/Shopify/identity_cache/blob/master/test/denormalized_has_many_test.rb#L124 ?

In that case it is using the active record association associated_record.item, instead of the cache accessor associated_record.fetch_item, so it shouldn't need modification. However, you could also add an assertion of assert_equal associated_record.fetch_item.object_id, item.object_id which should also pass with your change. The difference is that we don't want to store the record loaded from the cache in the active record association itself, we want to store it in the instance variable that the fetch_#{association} method accesses.

@roccoblues

Copy link
Copy Markdown
Author

Ok, I think I got it now. Please check again.

@spy-v2
spy-v2 Bot deleted the branch Shopify:master October 26, 2021 14:14
@spy-v2 spy-v2 Bot closed this Oct 26, 2021
@dylanahsmith

Copy link
Copy Markdown
Contributor

the default branch was renamed from master to main, which seemed to result in this PR being closed by the bot doing the rename. I'm not sure why it wasn't able to update this PRs target to main like in other PRs, but I can't reopen or update this PRs target branch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants