Skip to content

Commit c61b403

Browse files
committed
Allow mapping to be supplied as polymorphic: option
This removes the need for the `integer_type:` option when specify a polymorphic integer type association. This simplifies the configuration of the interger type polymorphic association. For example, here is a normal polymorphic association: ```ruby class Picture < ActiveRecord::Base belongs_to :imageable, polymorphic: true end class Employee < ActiveRecord::Base has_many :pictures, as: :imageable end class Product < ActiveRecord::Base has_many :pictures, as: :imageable end ``` And the same example using an integer type: ```ruby class Picture < ActiveRecord::Base include PolymorphicIntegerType::Extensions belongs_to :imageable, polymorphic: {1 => "Employee", 2 => "Product"} end class Employee < ActiveRecord::Base include PolymorphicIntegerType::Extensions has_many :pictures, as: :imageable end class Product < ActiveRecord::Base include PolymorphicIntegerType::Extensions has_many :pictures, as: :imageable end ``` The only difference between a normal polymorphic association and an integer type polymorphic relationship is that the integer-to-class mapping is passed directly to the `polymorphic:` option and the module is included in all associated models.
1 parent 4fbcace commit c61b403

4 files changed

Lines changed: 98 additions & 29 deletions

File tree

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,28 +51,29 @@ For Rails 3.2 use version < 2. Version >= 2 has been tested on Rails 4.2 and Rub
5151

5252
## Usage
5353

54-
Include `PolymorphicIntegerType::Extensions` into each associated model class then set the `integer_type` option on the appropriate association.
55-
56-
For the model where the `belongs_to` is defined, set `integer_type` to hash mapping integers to strings. This hash maps an integer in the database to a Ruby class.
54+
For the model where the `belongs_to` is defined, include `PolymorphicIntegerType::Extensions` and set the `polymorphic:` option to a hash that maps an integer stored in the database to the name of a Ruby class.
5755

5856
```ruby
5957
class Picture < ActiveRecord::Base
6058
include PolymorphicIntegerType::Extensions
61-
belongs_to :imageable, polymorphic: true, integer_type: {1 => "Employee", 2 => "Product"}
59+
60+
belongs_to :imageable, polymorphic: {1 => "Employee", 2 => "Product"}
6261
end
6362
```
6463

65-
Next, set `integer_type` to true on any related `has_many` or `has_one` associations:
64+
Next, include `PolymorphicIntegerType::Extensions` into any of the models that point back to the polymorphic integer type association (e.g., `Picture#imageable`) and add a [polymorphic association using `as:`](http://guides.rubyonrails.org/association_basics.html#polymorphic-associations).
6665

6766
```ruby
6867
class Employee < ActiveRecord::Base
6968
include PolymorphicIntegerType::Extensions
70-
has_many :pictures, as: :imageable, integer_type: true
69+
70+
has_many :pictures, as: :imageable
7171
end
7272

7373
class Product < ActiveRecord::Base
7474
include PolymorphicIntegerType::Extensions
75-
has_many :pictures, as: :imageable, integer_type: true
75+
76+
has_many :pictures, as: :imageable
7677
end
7778
```
7879

lib/polymorphic_integer_type/extensions.rb

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ def belongs_to(name, scope = nil, options = {})
77
options = scope if scope.kind_of? Hash
88
integer_type = options.delete :integer_type
99
super
10-
if options[:polymorphic] && integer_type
10+
if options[:polymorphic] && (integer_type || options[:polymorphic].is_a?(Hash))
1111
mapping =
12-
if integer_type == true
13-
PolymorphicIntegerType::Mapping[name]
12+
case integer_type
13+
when true then PolymorphicIntegerType::Mapping[name]
14+
when nil then options[:polymorphic]
1415
else
1516
integer_type
1617
end.dup
@@ -51,29 +52,22 @@ def belongs_to(name, scope = nil, options = {})
5152

5253
def remove_type_and_establish_mapping(name, options, scope)
5354
integer_type = options.delete :integer_type
55+
polymorphic_type_mapping = retrieve_polymorphic_type_mapping(
56+
polymorphic_type: options[:as],
57+
class_name: options[:class_name] || name.to_s.classify
58+
)
5459

55-
if options[:as] && integer_type
60+
if options[:as] && (polymorphic_type_mapping || integer_type)
5661
poly_type = options.delete(:as)
57-
class_name = options[:class_name] || name.to_s.classify
58-
mapping = begin
59-
belongs_to_class = class_name.constantize
60-
method_name = "#{poly_type}_type_mapping"
61-
if belongs_to_class.respond_to?(method_name)
62-
belongs_to_class.public_send(method_name)
63-
end
64-
rescue NameError => e
65-
STDERR.puts "Failed to load belongs_to class: #{e.message}"
66-
end
67-
68-
mapping ||= PolymorphicIntegerType::Mapping[poly_type]
69-
if mapping == nil
62+
polymorphic_type_mapping ||= PolymorphicIntegerType::Mapping[poly_type]
63+
if polymorphic_type_mapping == nil
7064
raise "Polymorphic type mapping missing for #{poly_type.inspect}"
7165
end
7266

73-
klass_mapping = (mapping || {}).key(sti_name)
67+
klass_mapping = (polymorphic_type_mapping || {}).key(sti_name)
7468

7569
if klass_mapping == nil
76-
raise "Class not found for #{sti_name.inspect} in polymorphic type mapping: #{mapping}"
70+
raise "Class not found for #{sti_name.inspect} in polymorphic type mapping: #{polymorphic_type_mapping}"
7771
end
7872

7973
options[:foreign_key] ||= "#{poly_type}_id"
@@ -89,6 +83,22 @@ def remove_type_and_establish_mapping(name, options, scope)
8983
end
9084
end
9185

86+
def retrieve_polymorphic_type_mapping(polymorphic_type:, class_name:)
87+
return if polymorphic_type == nil
88+
89+
belongs_to_class = begin
90+
class_name.constantize
91+
rescue NameError
92+
# Class not found
93+
end
94+
95+
method_name = "#{polymorphic_type}_type_mapping"
96+
97+
if belongs_to_class && belongs_to_class.respond_to?(method_name)
98+
belongs_to_class.public_send(method_name)
99+
end
100+
end
101+
92102
def has_many(name, scope = nil, options = {}, &extension)
93103
if scope.kind_of? Hash
94104
options = scope

spec/polymorphic_integer_type_spec.rb

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class InlineLink < ActiveRecord::Base
156156

157157
belongs_to :source, polymorphic: true, integer_type: {10 => "Person", 11 => "InlineAnimal"}
158158
belongs_to :target, polymorphic: true, integer_type: {10 => "Food", 13 => "InlineDrink"}
159+
belongs_to :normal_target, polymorphic: true
159160
end
160161

161162
class InlineAnimal < ActiveRecord::Base
@@ -164,20 +165,20 @@ class InlineAnimal < ActiveRecord::Base
164165
self.table_name = "animals"
165166

166167
belongs_to :owner, class_name: "Person"
167-
has_many :source_links, as: :source, class_name: "InlineLink", integer_type: true
168+
has_many :source_links, as: :source, class_name: "InlineLink"
168169
end
169170

170171
class InlineDrink < ActiveRecord::Base
171172
include PolymorphicIntegerType::Extensions
172173

173174
self.table_name = "drinks"
174175

175-
has_many :inline_links, as: :target, integer_type: true
176+
has_many :inline_links, as: :target
176177
end
177178

178179
let!(:animal) { InlineAnimal.create!(name: "Lucy") }
179180
let!(:drink) { InlineDrink.create!(name: "Water") }
180-
let!(:link) { InlineLink.create!(source: animal, target: drink) }
181+
let!(:link) { InlineLink.create!(source: animal, target: drink, normal_target: drink) }
181182

182183
let(:source) { animal }
183184
let(:target) { drink }
@@ -196,5 +197,60 @@ class InlineDrink < ActiveRecord::Base
196197
expect(link.target_id).to eq(drink.id)
197198
expect(link[:target_type]).to eq(13)
198199
end
200+
201+
it "doesn't break string type polymorphic associations" do
202+
expect(link.normal_target).to eq(drink)
203+
expect(link.normal_target_type).to eq("InlineDrink")
204+
end
205+
end
206+
207+
context "when mapping assigned to `polymorphic` option on belongs_to model" do
208+
class InlineLink2 < ActiveRecord::Base
209+
include PolymorphicIntegerType::Extensions
210+
211+
self.table_name = "links"
212+
213+
belongs_to :source, polymorphic: {10 => "Person", 11 => "InlineAnimal2"}
214+
belongs_to :target, polymorphic: {10 => "Food", 13 => "InlineDrink2"}
215+
belongs_to :normal_target, polymorphic: true
216+
end
217+
218+
class InlineAnimal2 < ActiveRecord::Base
219+
include PolymorphicIntegerType::Extensions
220+
221+
self.table_name = "animals"
222+
223+
has_many :source_links, as: :source, class_name: "InlineLink2"
224+
end
225+
226+
class InlineDrink2 < ActiveRecord::Base
227+
include PolymorphicIntegerType::Extensions
228+
229+
self.table_name = "drinks"
230+
231+
has_many :inline_links2, as: :target
232+
end
233+
234+
let!(:animal) { InlineAnimal2.create!(name: "Lucy") }
235+
let!(:drink) { InlineDrink2.create!(name: "Water") }
236+
let!(:link) { InlineLink2.create!(source: animal, target: drink, normal_target: drink) }
237+
238+
let(:source) { animal }
239+
let(:target) { drink }
240+
241+
include_examples "proper source"
242+
include_examples "proper target"
243+
244+
it "pulls mapping from given hash" do
245+
expect(link.source_id).to eq(animal.id)
246+
expect(link[:source_type]).to eq(11)
247+
expect(link.target_id).to eq(drink.id)
248+
expect(link[:target_type]).to eq(13)
249+
end
250+
251+
it "doesn't break string type polymorphic associations" do
252+
expect(link.normal_target).to eq(drink)
253+
expect(link.normal_target_type).to eq("InlineDrink2")
254+
end
199255
end
200256
end

spec/support/migrations/1_create_link_table.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ def up
44
create_table :links do |t|
55
t.integer :target_id
66
t.integer :target_type
7+
t.integer :normal_target_id
8+
t.string :normal_target_type
79
t.integer :source_id
810
t.integer :source_type
911
end

0 commit comments

Comments
 (0)