Skip to content

Commit 1ed2191

Browse files
djmazeobrie
authored andcommitted
Respect custom primary_key configurations in ActiveRecord models
1 parent b023a26 commit 1ed2191

14 files changed

+159
-23
lines changed

CHANGELOG.rdoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
== master
22

3+
* Respect custom primary_key configurations in ActiveRecord models [Martin Honermeyer]
34
* Fix tests failing on Rails 2.3.3+
45
* Release gems via rake-gemcutter instead of rubyforge
56

lib/enumerate_by.rb

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -265,29 +265,31 @@ module Bootstrapped
265265
# Otherwise, any changes to that column remain there.
266266
def bootstrap(*records)
267267
uncached do
268+
primary_key = self.primary_key.to_sym
269+
268270
# Remove records that are no longer being used
269271
records.flatten!
270-
ids = records.map {|record| record[:id]}.compact
271-
delete_all(ids.any? ? ['id NOT IN (?)', ids] : nil)
272+
ids = records.map {|record| record[primary_key]}.compact
273+
delete_all(ids.any? ? ["#{primary_key} NOT IN (?)", ids] : nil)
272274

273275
# Find remaining existing records (to be updated)
274-
existing = all.inject({}) {|existing, record| existing[record.id] = record; existing}
276+
existing = all.inject({}) {|existing, record| existing[record.send(primary_key)] = record; existing}
275277

276278
records.map! do |attributes|
277279
attributes.symbolize_keys!
278280
defaults = attributes.delete(:defaults)
279281

280282
# Update with new attributes
281283
record =
282-
if record = existing[attributes[:id]]
284+
if record = existing[attributes[primary_key]]
283285
attributes.merge!(defaults.delete_if {|attribute, value| record.send("#{attribute}?")}) if defaults
284286
record.attributes = attributes
285287
record
286288
else
287289
attributes.merge!(defaults) if defaults
288290
new(attributes)
289291
end
290-
record.id = attributes[:id]
292+
record.send("#{primary_key}=", attributes[primary_key])
291293

292294
# Force failed saves to stop execution
293295
record.save!
@@ -317,27 +319,29 @@ def bootstrap(*records)
317319
#
318320
# See EnumerateBy::Bootstrapped#bootstrap for information about usage.
319321
def fast_bootstrap(*records)
322+
primary_key = self.primary_key.to_sym
323+
320324
# Remove records that are no longer being used
321325
records.flatten!
322-
ids = records.map {|record| record[:id]}.compact
323-
delete_all(ids.any? ? ['id NOT IN (?)', ids] : nil)
326+
ids = records.map {|record| record[primary_key]}.compact
327+
delete_all(ids.any? ? ["#{primary_key} NOT IN (?)", ids] : nil)
324328

325329
# Find remaining existing records (to be updated)
326330
quoted_table_name = self.quoted_table_name
327-
existing = connection.select_all("SELECT * FROM #{quoted_table_name}").inject({}) {|existing, record| existing[record['id'].to_i] = record; existing}
331+
existing = connection.select_all("SELECT * FROM #{quoted_table_name}").inject({}) {|existing, record| existing[record[primary_key.to_s].to_i] = record; existing}
328332

329333
records.each do |attributes|
330334
attributes.stringify_keys!
331335
if defaults = attributes.delete('defaults')
332336
defaults.stringify_keys!
333337
end
334338

335-
id = attributes['id']
339+
id = attributes[primary_key.to_s]
336340
if existing_attributes = existing[id]
337341
# Record exists: Update attributes
338-
attributes.delete('id')
342+
attributes.delete(primary_key.to_s)
339343
attributes.merge!(defaults.delete_if {|attribute, value| !existing_attributes[attribute].nil?}) if defaults
340-
update_all(attributes, :id => id)
344+
update_all(attributes, primary_key => id)
341345
else
342346
# Record doesn't exist: create new one
343347
attributes.merge!(defaults) if defaults

lib/enumerate_by/extensions/associations.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,19 @@ def belongs_to_with_enumerations(association_id, options = {})
7575
primary_key_name = reflection.primary_key_name
7676
class_name = reflection.class_name
7777
klass = reflection.klass
78+
klass_primary_key = klass.primary_key.to_sym
7879

7980
# Inclusion scopes
8081
%W(with_#{name} with_#{name.to_s.pluralize}).each do |scope_name|
8182
named_scope scope_name.to_sym, lambda {|*enumerators| {
82-
:conditions => {primary_key_name => klass.find_all_by_enumerator!(enumerators).map(&:id)}
83+
:conditions => {primary_key_name => klass.find_all_by_enumerator!(enumerators).map(&klass_primary_key)}
8384
}}
8485
end
8586

8687
# Exclusion scopes
8788
%W(without_#{name} without_#{name.to_s.pluralize}).each do |scope_name|
8889
named_scope scope_name.to_sym, lambda {|*enumerators| {
89-
:conditions => ["#{primary_key_name} NOT IN (?)", klass.find_all_by_enumerator!(enumerators).map(&:id)]
90+
:conditions => ["#{primary_key_name} NOT IN (?)", klass.find_all_by_enumerator!(enumerators).map(&klass_primary_key)]
9091
}}
9192
end
9293

lib/enumerate_by/extensions/base_conditions.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,12 @@ def replace_enumerations_in_hash(attrs, allow_multiple = true) #:nodoc:
104104
def enumerator_options_for(name, enumerator, allow_multiple = true)
105105
if reflection = reflect_on_enumeration(name)
106106
klass = reflection.klass
107+
klass_primary_key = klass.primary_key.to_sym
107108
attribute = reflection.primary_key_name
108109
id = if allow_multiple && enumerator.is_a?(Array)
109-
klass.find_all_by_enumerator!(enumerator).map(&:id)
110+
klass.find_all_by_enumerator!(enumerator).map(&klass_primary_key)
110111
else
111-
klass.find_by_enumerator!(enumerator).id
112+
klass.find_by_enumerator!(enumerator).send(klass_primary_key)
112113
end
113114

114115
{attribute => id}

test/app_root/app/models/car.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
class Car < ActiveRecord::Base
22
belongs_to :color
3+
belongs_to :legacy_color
34
belongs_to :feature, :polymorphic => true
45
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class LegacyColor < ActiveRecord::Base
2+
set_primary_key :uid
3+
4+
enumerate_by :name
5+
end

test/app_root/db/migrate/002_create_cars.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class CreateCars < ActiveRecord::Migration
22
def self.up
33
create_table :cars do |t|
44
t.string :name
5-
t.references :color
5+
t.references :color, :legacy_color
66
t.references :feature, :class_name => 'Color', :polymorphic => true
77
end
88
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class CreateLegacyColors < ActiveRecord::Migration
2+
def self.up
3+
create_table :legacy_colors, :primary_key => 'uid' do |t|
4+
t.string :name, :null => false
5+
t.string :html
6+
end
7+
end
8+
9+
def self.down
10+
drop_table :legacy_colors
11+
end
12+
end

test/factory.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ def create_record(model, *args)
3434
end
3535

3636
build Car do |attributes|
37-
attributes[:color] = create_color unless attributes.include?(:color)
3837
attributes.reverse_merge!(
3938
:name => 'Ford Mustang'
4039
)
@@ -52,6 +51,12 @@ def create_record(model, *args)
5251
)
5352
end
5453

54+
build LegacyColor do |attributes|
55+
attributes.reverse_merge!(
56+
:name => 'red'
57+
)
58+
end
59+
5560
build Order do |attributes|
5661
attributes[:car_part] = create_car_part unless attributes.include?(:car_part)
5762
attributes.reverse_merge!(

test/unit/assocations_test.rb

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def test_should_allow_nil
3333
end
3434

3535
def test_should_track_associations
36-
expected = {'color_id' => 'color'}
36+
expected = {'color_id' => 'color', 'legacy_color_id' => 'legacy_color'}
3737
assert_equal expected, Car.enumeration_associations
3838
end
3939
end
@@ -73,3 +73,30 @@ def test_should_not_create_named_scopes
7373
assert !Car.respond_to?(:without_features)
7474
end
7575
end
76+
77+
class ModelWithEnumerationScopesUsingCustomPrimaryKeyTest < ActiveRecord::TestCase
78+
def setup
79+
@red = create_legacy_color(:name => 'red')
80+
@blue = create_legacy_color(:name => 'blue')
81+
@red_car = create_car(:name => 'Ford Mustang', :legacy_color => @red)
82+
@blue_car = create_car(:name => 'Ford Mustang', :legacy_color => @blue)
83+
end
84+
85+
def test_should_have_inclusion_scope_for_single_enumerator
86+
assert_equal [@red_car], Car.with_legacy_color('red')
87+
assert_equal [@blue_car], Car.with_legacy_color('blue')
88+
end
89+
90+
def test_should_have_inclusion_scope_for_multiple_enumerators
91+
assert_equal [@red_car, @blue_car], Car.with_legacy_color('red', 'blue')
92+
end
93+
94+
def test_should_have_exclusion_scope_for_single_enumerator
95+
assert_equal [@blue_car], Car.without_legacy_color('red')
96+
assert_equal [@red_car], Car.without_legacy_color('blue')
97+
end
98+
99+
def test_should_have_exclusion_scope_for_multiple_enumerators
100+
assert_equal [], Car.without_legacy_colors('red', 'blue')
101+
end
102+
end

test/unit/base_conditions_test.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ def test_should_still_allow_multiple_non_enumerations_in_find_conditions
5757
end
5858
end
5959

60+
class EnumerationWithCustomPrimaryKeyAndFinderConditionsTest < ActiveRecord::TestCase
61+
def setup
62+
@red = create_legacy_color(:name => 'red')
63+
@blue = create_legacy_color(:name => 'blue')
64+
@red_car = create_car(:name => 'Ford Mustang', :legacy_color => @red)
65+
@blue_car = create_car(:name => 'Ford Mustang', :legacy_color => @blue)
66+
end
67+
68+
def test_should_replace_enumerations_in_dynamic_finders
69+
assert_equal @red_car, Car.find_by_legacy_color('red')
70+
end
71+
72+
def test_should_replace_enumerations_in_find_conditions
73+
assert_equal @red_car, Car.first(:conditions => {:legacy_color => 'red'})
74+
end
75+
end
76+
6077
class EnumerationWithFinderUpdatesTest < ActiveRecord::TestCase
6178
def setup
6279
@red = create_color(:name => 'red')
@@ -86,3 +103,17 @@ def test_should_not_replace_multiple_enumerations_in_update_conditions
86103
assert_equal @red, @red_car.color
87104
end
88105
end
106+
107+
class EnumerationWithCustomPrimaryKeyAndFinderUpdatesTest < ActiveRecord::TestCase
108+
def setup
109+
@red = create_legacy_color(:name => 'red')
110+
@blue = create_legacy_color(:name => 'blue')
111+
@red_car = create_car(:name => 'Ford Mustang', :legacy_color => @red)
112+
end
113+
114+
def test_should_replace_enumerations_in_update_conditions
115+
Car.update_all({:legacy_color => 'blue'}, :name => 'Ford Mustang')
116+
@red_car.reload
117+
assert_equal @blue, @red_car.legacy_color
118+
end
119+
end

test/unit/enumerate_by_test.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,28 @@ def test_should_update_default_attributes_if_not_defined
448448
end
449449
end
450450

451+
class EnumerationWithCustomPrimaryKeyBootstrappedTest < ActiveRecord::TestCase
452+
def setup
453+
@red, @green = LegacyColor.bootstrap(
454+
{:uid => 1, :name => 'red'},
455+
{:uid => 2, :name => 'green'}
456+
)
457+
end
458+
459+
def test_should_not_raise_exception_if_primary_key_not_specified
460+
assert_nothing_raised { LegacyColor.bootstrap({:name => 'red'}, {:name => 'green'}) }
461+
assert_equal 2, LegacyColor.count
462+
end
463+
464+
def test_should_create_records
465+
assert_equal @red, LegacyColor.find(1)
466+
assert_equal 'red', @red.name
467+
468+
assert_equal @green, LegacyColor.find(2)
469+
assert_equal 'green', @green.name
470+
end
471+
end
472+
451473
class EnumerationFastBootstrappedTest < ActiveRecord::TestCase
452474
def setup
453475
@result = Color.fast_bootstrap(
@@ -546,3 +568,23 @@ def test_should_update_default_attributes_if_not_defined
546568
assert_equal '#00ff00', @green.html
547569
end
548570
end
571+
572+
class EnumerationWithCustomPrimaryKeyFastBootstrappedTest < ActiveRecord::TestCase
573+
def setup
574+
@result = LegacyColor.fast_bootstrap(
575+
{:uid => 1, :name => 'red'},
576+
{:uid => 2, :name => 'green'}
577+
)
578+
end
579+
580+
def test_should_not_raise_exception_if_primary_key_not_specified
581+
assert_nothing_raised { LegacyColor.fast_bootstrap({:name => 'red'}, {:name => 'green'}) }
582+
assert_equal 2, LegacyColor.count
583+
end
584+
585+
def test_should_create_records
586+
assert @result
587+
assert_not_nil LegacyColor.find_by_name('red')
588+
assert_not_nil LegacyColor.find_by_name('green')
589+
end
590+
end

test/unit/serializer_test.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ def setup
88
end
99

1010
def test_should_include_enumerations_in_serializable_attribute_names
11-
assert_equal %w(color feature_id feature_type id name), @serializer.serializable_attribute_names
11+
assert_equal %w(color feature_id feature_type id legacy_color name), @serializer.serializable_attribute_names
1212
end
1313

1414
def test_should_typecast_serializable_record
1515
expected = {
1616
'color' => 'red',
1717
'feature_id' => nil,
1818
'feature_type' => nil,
19+
'legacy_color' => nil,
1920
'id' => @car.id,
2021
'name' => 'Ford Mustang'
2122
}
@@ -32,14 +33,15 @@ def setup
3233
end
3334

3435
def test_should_not_include_enumerations_in_serializable_attribute_names
35-
assert_equal %w(color_id feature_id feature_type id name), @serializer.serializable_attribute_names
36+
assert_equal %w(color_id feature_id feature_type id legacy_color_id name), @serializer.serializable_attribute_names
3637
end
3738

3839
def test_should_not_typecast_serializable_record
3940
expected = {
4041
'color_id' => @red.id,
4142
'feature_id' => nil,
4243
'feature_type' => nil,
44+
'legacy_color_id' => nil,
4345
'id' => @car.id,
4446
'name' => 'Ford Mustang'
4547
}
@@ -98,13 +100,14 @@ def setup
98100
end
99101

100102
def test_should_not_include_enumeration_in_serializable_attribute_names
101-
assert_equal %w(feature_id feature_type id name), @serializer.serializable_attribute_names
103+
assert_equal %w(feature_id feature_type id legacy_color name), @serializer.serializable_attribute_names
102104
end
103105

104106
def test_should_not_include_enumeration_in_serializable_record
105107
expected = {
106108
'feature_id' => nil,
107109
'feature_type' => nil,
110+
'legacy_color' => nil,
108111
'id' => @car.id,
109112
'name' => 'Ford Mustang'
110113
}
@@ -121,13 +124,14 @@ def setup
121124
end
122125

123126
def test_should_not_include_enumeration_in_serializable_attribute_names
124-
assert_equal %w(feature_id feature_type id name), @serializer.serializable_attribute_names
127+
assert_equal %w(feature_id feature_type id legacy_color name), @serializer.serializable_attribute_names
125128
end
126129

127130
def test_should_not_include_enumeration_in_serializable_record
128131
expected = {
129132
'feature_id' => nil,
130133
'feature_type' => nil,
134+
'legacy_color' => nil,
131135
'id' => @car.id,
132136
'name' => 'Ford Mustang'
133137
}
@@ -144,7 +148,7 @@ def setup
144148
end
145149

146150
def test_should_not_include_enumeration_in_serializable_attribute_names
147-
assert_equal %w(color_id feature_id feature_type id name), @serializer.serializable_attribute_names
151+
assert_equal %w(color_id feature_id feature_type id legacy_color name), @serializer.serializable_attribute_names
148152
end
149153

150154
def test_should_include_entire_enumeration_in_serializable_record
@@ -157,6 +161,7 @@ def test_should_include_entire_enumeration_in_serializable_record
157161
'color_id' => @red.id,
158162
'feature_id' => nil,
159163
'feature_type' => nil,
164+
'legacy_color' => nil,
160165
'id' => @car.id,
161166
'name' => 'Ford Mustang'
162167
}

test/unit/xml_serializer_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def test_should_be_able_to_convert_to_xml
6262
<feature-id type="integer" nil="true"></feature-id>
6363
<feature-type nil="true"></feature-type>
6464
<id type="integer">#{@car.id}</id>
65+
<legacy-color nil="true"></legacy-color>
6566
<name>Ford Mustang</name>
6667
</car>
6768
eos

0 commit comments

Comments
 (0)