Generate unique members of a composite key in fixtures

Typically, a fixture's label is hashed into a single unique identifier
that sufficiently represent the respective fixture. In composite key
models, there's still only one label, but a requirement to populate
values for an arbitrary number of columns. Until now, these columns
would all take on the same value, but these may appear odd to an end
user and lead to confusion. This commit uses the index of the column in
the composite key to shift the hash in a deterministic way. In this
case, we bit shift the hashed value index-times and modulo it with the
max. This differentiates components of the key enough that there doesn't
appear to be a correlation.
This commit is contained in:
Paarth Madan 2023-03-29 19:42:53 -04:00
parent 4d171a4bde
commit f7062f2676
3 changed files with 37 additions and 5 deletions

@ -87,7 +87,7 @@ def fill_row_model_attributes
return unless model_class
fill_timestamps
interpolate_label
generate_primary_key
model_class.composite_primary_key? ? generate_composite_primary_key : generate_primary_key
resolve_enums
resolve_sti_reflections
end
@ -117,14 +117,28 @@ def interpolate_label
end
def generate_primary_key
# generate a primary key if necessary
Array(model_metadata.primary_key_name).each do |pk|
next if !model_metadata.has_column?(pk) || @row.include?(pk)
pk = model_metadata.primary_key_name
unless column_defined?(pk)
@row[pk] = ActiveRecord::FixtureSet.identify(@label, model_metadata.column_type(pk))
end
end
def generate_composite_primary_key
id = ActiveRecord::FixtureSet.identify(@label)
model_metadata.primary_key_name.each_with_index do |column, index|
next if column_defined?(column)
raise "Automatic key generation assumes columns of type Integer." unless model_metadata.column_type(column) == :integer
# Shift label identifier index-#-of-times to differentiate sub-components in deterministic manner.
@row[column] = (id << index) % ActiveRecord::FixtureSet::MAX_ID
end
end
def column_defined?(col)
!model_metadata.has_column?(col) || @row.include?(col)
end
def resolve_enums
reflection_class.defined_enums.each do |name, values|
if @row.include?(name)

@ -1643,7 +1643,16 @@ def readonly_config
end
class CompositePkFixturesTest < ActiveRecord::TestCase
fixtures :cpk_orders, :cpk_books
fixtures :cpk_orders, :cpk_books, :authors
def test_generates_composite_primary_key_for_partially_filled_fixtures
david = authors(:david)
david_cpk_book = cpk_books(:cpk_known_author_david_book)
assert_not_empty(david_cpk_book.id.compact)
assert_equal david.id, david_cpk_book.author_id
assert_not_nil david_cpk_book.number
end
def test_generates_composite_primary_key_ids
assert_not_empty(cpk_orders(:cpk_groceries_order_1).id.compact)
@ -1651,5 +1660,9 @@ def test_generates_composite_primary_key_ids
assert_not_nil(cpk_books(:cpk_great_author_first_book).author_id)
assert_not_nil(cpk_books(:cpk_great_author_first_book).number)
end
def test_generates_composite_primary_key_with_unique_components
assert_equal 2, cpk_orders(:cpk_groceries_order_1).id.uniq.size
end
end
end

@ -12,3 +12,8 @@ cpk_great_author_second_book:
cpk_famous_author_first_book:
title: "Ruby on Rails"
revision: 1
cpk_known_author_david_book:
author_id: 1
title: "David's CPK Book"
revision: 1