Merge pull request #47230 from Shopify/composite-foreign-key-assoc-assignment

Support composite foreign key association assignments
This commit is contained in:
Eileen M. Uchitelle 2023-02-02 16:34:41 -05:00 committed by GitHub
commit 5defeececb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 7 deletions

@ -119,10 +119,13 @@ def require_counter_update?
end
def replace_keys(record, force: false)
target_key = record ? record._read_attribute(primary_key(record.class)) : nil
target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
reflection_fk = Array(reflection.foreign_key)
if force || owner._read_attribute(reflection.foreign_key) != target_key
owner[reflection.foreign_key] = target_key
if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
reflection_fk.zip(target_key_values).each do |key, value|
owner[key] = value
end
end
end

@ -22,8 +22,15 @@ def nullified_owner_attributes
def set_owner_attributes(record)
return if options[:through]
key = owner._read_attribute(reflection.join_foreign_key)
record._write_attribute(reflection.join_primary_key, key)
primary_key_attribute_names = Array(reflection.join_primary_key)
foreign_key_attribute_names = Array(reflection.join_foreign_key)
primary_key_foreign_key_pairs = primary_key_attribute_names.zip(foreign_key_attribute_names)
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
value = owner._read_attribute(foreign_key)
record._write_attribute(primary_key, value)
end
if reflection.type
record._write_attribute(reflection.type, owner.class.polymorphic_name)

@ -505,8 +505,14 @@ def save_belongs_to_association(reflection)
saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
if association.updated?
association_id = record.public_send(reflection.options[:primary_key] || :id)
self[reflection.foreign_key] = association_id unless self[reflection.foreign_key] == association_id
primary_key = Array(reflection.options[:primary_key] || record.class.query_constraints_list)
foreign_key = Array(reflection.foreign_key)
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
association_id = record.public_send(primary_key)
self[foreign_key] = association_id unless self[foreign_key] == association_id
end
association.loaded!
end

@ -165,6 +165,72 @@ def test_model_with_composite_query_constraints_has_many_association_sql
assert_match(/#{Regexp.escape(Sharded::Comment.connection.quote_table_name("sharded_comments.blog_post_id"))} =/, sql)
assert_match(/#{Regexp.escape(Sharded::Comment.connection.quote_table_name("sharded_comments.blog_id"))} =/, sql)
end
def test_append_composite_foreign_key_has_many_association
blog_post = sharded_blog_posts(:great_post_blog_one)
comment = Sharded::Comment.new(body: "Great post! :clap:")
comment.save
blog_post.comments << comment
assert_includes(blog_post.comments, comment)
assert_equal(blog_post.id, comment.blog_post_id)
assert_equal(blog_post.blog_id, comment.blog_id)
end
def test_assign_persisted_composite_foreign_key_belongs_to_association
comment = sharded_comments(:great_comment_blog_post_one)
another_blog = sharded_blogs(:sharded_blog_two)
assert_not_equal(comment.blog_id, another_blog.id)
blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
blog_post.save
comment.blog_post = blog_post
assert_equal(blog_post, comment.blog_post)
assert_equal(comment.blog_id, blog_post.blog_id)
assert_equal(another_blog.id, comment.blog_id)
assert_equal(comment.blog_post_id, blog_post.id)
end
def test_assign_composite_foreign_key_belongs_to_association
comment = sharded_comments(:great_comment_blog_post_one)
another_blog = sharded_blogs(:sharded_blog_two)
assert_not_equal(comment.blog_id, another_blog.id)
blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
comment.blog_post = blog_post
assert_equal(blog_post, comment.blog_post)
assert_equal(comment.blog_id, blog_post.blog_id)
assert_equal(another_blog.id, comment.blog_id)
end
def test_append_composite_foreign_key_has_many_association_with_autosave
blog_post = sharded_blog_posts(:great_post_blog_one)
comment = Sharded::Comment.new(body: "Great post! :clap:")
blog_post.comments << comment
assert_predicate(comment, :persisted?)
assert_includes(blog_post.comments, comment)
assert_equal(blog_post.id, comment.blog_post_id)
assert_equal(blog_post.blog_id, comment.blog_id)
end
def test_assign_composite_foreign_key_belongs_to_association_with_autosave
comment = sharded_comments(:great_comment_blog_post_one)
another_blog = sharded_blogs(:sharded_blog_two)
assert_not_equal(comment.blog_id, another_blog.id)
blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
comment.blog_post = blog_post
comment.save
assert_predicate(blog_post, :persisted?)
assert_equal(blog_post, comment.blog_post)
assert_equal(comment.blog_id, blog_post.blog_id)
assert_equal(another_blog.id, comment.blog_id)
assert_equal(comment.blog_post_id, blog_post.id)
end
end
class AssociationProxyTest < ActiveRecord::TestCase