Skip to content

Commit 4fbcace

Browse files
committed
Allow inline polymorphic mappings
This moves the mapping data into the model and allows different models that use the same relationship name to have different mappings. This is how it used to work: ```ruby class Picture < ActiveRecord::Base include PolymorphicIntegerType::Extensions belongs_to :imageable, polymorphic: true, integer_type: {1 => "Employee", 2 => "Product"} end class Employee < ActiveRecord::Base include PolymorphicIntegerType::Extensions has_many :pictures, as: :imageable, integer_type: true end class Product < ActiveRecord::Base include PolymorphicIntegerType::Extensions has_many :pictures, as: :imageable, integer_type: true end PolymorphicIntegerType::Mapping.configuration do |config| config.add :imageable, {1 => "Employee", 2 => "Product" } end ``` This is how it works now: ```ruby class Picture < ActiveRecord::Base include PolymorphicIntegerType::Extensions belongs_to :imageable, polymorphic: true, integer_type: {1 => "Employee", 2 => "Product"} end class Employee < ActiveRecord::Base include PolymorphicIntegerType::Extensions has_many :pictures, as: :imageable, integer_type: true end class Product < ActiveRecord::Base include PolymorphicIntegerType::Extensions has_many :pictures, as: :imageable, integer_type: true end ``` The old way still works, allowing for smooth migration to the inline style.
1 parent 07a247b commit 4fbcace

3 files changed

Lines changed: 97 additions & 13 deletions

File tree

README.md

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

5252
## Usage
5353

54-
The gem is pretty straightforward to use.
54+
Include `PolymorphicIntegerType::Extensions` into each associated model class then set the `integer_type` option on the appropriate association.
5555

56-
First, include the extensions module and add the `integer_type` option to the associations that are going to be using this. (That way it will play nicely with polymorphic associations whose type you would rather leave as a string.)
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.
5757

5858
```ruby
5959
class Picture < ActiveRecord::Base
6060
include PolymorphicIntegerType::Extensions
61-
belongs_to :imageable, polymorphic: true, integer_type: true
61+
belongs_to :imageable, polymorphic: true, integer_type: {1 => "Employee", 2 => "Product"}
6262
end
63+
```
6364

65+
Next, set `integer_type` to true on any related `has_many` or `has_one` associations:
66+
67+
```ruby
6468
class Employee < ActiveRecord::Base
6569
include PolymorphicIntegerType::Extensions
6670
has_many :pictures, as: :imageable, integer_type: true
@@ -72,18 +76,20 @@ class Product < ActiveRecord::Base
7276
end
7377
```
7478

75-
Second, you need to create a mapping for the polymorphic associations. This should be loaded before the models. Putting it in an initializer is good (`config/initializers/polymorphic_type_mapping.rb`)
79+
### External mappings
80+
81+
You can also store polymorphic type mappings separate from your models. This should be loaded before the models. Putting it in an initializer is one way to do this (e.g., `config/initializers/polymorphic_type_mapping.rb`)
7682

7783
```ruby
7884
PolymorphicIntegerType::Mapping.configuration do |config|
79-
80-
config.add :imageable, {1 => "Employee", 2 => "Product" }
81-
85+
config.add :imageable, {1 => "Employee", 2 => "Product" }
8286
end
8387
```
8488

8589
Note: The mapping here can start from whatever integer you wish, but I would advise not using 0. The reason being that if you had a new class, for instance `Avatar`, and also wanted to use this polymorphic association but forgot to include it in the mapping, it would effectively get `to_i` called on it and stored in the database. `"Avatar".to_i == 0`, so if your mapping included 0, this would create a weird bug.
8690

91+
### Migrating an existing association
92+
8793
If you want to convert a polymorphic association that is already a string, you'll need to set up a migration. (Assuming SQL for the time being, but this should be pretty straightforward.)
8894

8995
```ruby

lib/polymorphic_integer_type/extensions.rb

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,20 @@ def belongs_to(name, scope = nil, options = {})
88
integer_type = options.delete :integer_type
99
super
1010
if options[:polymorphic] && integer_type
11-
mapping = PolymorphicIntegerType::Mapping[name]
11+
mapping =
12+
if integer_type == true
13+
PolymorphicIntegerType::Mapping[name]
14+
else
15+
integer_type
16+
end.dup
17+
1218
foreign_type = reflections[name.to_s].foreign_type
13-
self._polymorphic_foreign_types << foreign_type
19+
_polymorphic_foreign_types << foreign_type
20+
21+
# Required way to dynamically define a class method on the model
22+
singleton_class.__send__(:define_method, "#{foreign_type}_mapping") do
23+
mapping
24+
end
1425

1526
define_method foreign_type do
1627
t = super()
@@ -40,11 +51,30 @@ def belongs_to(name, scope = nil, options = {})
4051

4152
def remove_type_and_establish_mapping(name, options, scope)
4253
integer_type = options.delete :integer_type
54+
4355
if options[:as] && integer_type
4456
poly_type = options.delete(:as)
45-
mapping = PolymorphicIntegerType::Mapping[poly_type]
46-
klass_mapping = (mapping||{}).key self.sti_name
47-
raise "Polymorphic Class Mapping is missing for #{poly_type}" unless klass_mapping
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
70+
raise "Polymorphic type mapping missing for #{poly_type.inspect}"
71+
end
72+
73+
klass_mapping = (mapping || {}).key(sti_name)
74+
75+
if klass_mapping == nil
76+
raise "Class not found for #{sti_name.inspect} in polymorphic type mapping: #{mapping}"
77+
end
4878

4979
options[:foreign_key] ||= "#{poly_type}_id"
5080
foreign_type = options.delete(:foreign_type) || "#{poly_type}_type"

spec/polymorphic_integer_type_spec.rb

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,53 @@
148148
end
149149
end
150150

151-
context "when "
151+
context "when mapping is given inline in the belongs_to model" do
152+
class InlineLink < ActiveRecord::Base
153+
include PolymorphicIntegerType::Extensions
154+
155+
self.table_name = "links"
156+
157+
belongs_to :source, polymorphic: true, integer_type: {10 => "Person", 11 => "InlineAnimal"}
158+
belongs_to :target, polymorphic: true, integer_type: {10 => "Food", 13 => "InlineDrink"}
159+
end
160+
161+
class InlineAnimal < ActiveRecord::Base
162+
include PolymorphicIntegerType::Extensions
163+
164+
self.table_name = "animals"
165+
166+
belongs_to :owner, class_name: "Person"
167+
has_many :source_links, as: :source, class_name: "InlineLink", integer_type: true
168+
end
169+
170+
class InlineDrink < ActiveRecord::Base
171+
include PolymorphicIntegerType::Extensions
172+
173+
self.table_name = "drinks"
174+
175+
has_many :inline_links, as: :target, integer_type: true
176+
end
177+
178+
let!(:animal) { InlineAnimal.create!(name: "Lucy") }
179+
let!(:drink) { InlineDrink.create!(name: "Water") }
180+
let!(:link) { InlineLink.create!(source: animal, target: drink) }
181+
182+
let(:source) { animal }
183+
let(:target) { drink }
184+
185+
include_examples "proper source"
186+
include_examples "proper target"
187+
188+
it "creates foreign_type mapping method" do
189+
expect(Link.source_type_mapping).to eq({0 => "Person", 1 => "Animal"})
190+
expect(InlineLink.source_type_mapping).to eq({10 => "Person", 11 => "InlineAnimal"})
191+
end
192+
193+
it "pulls mapping from given hash" do
194+
expect(link.source_id).to eq(animal.id)
195+
expect(link[:source_type]).to eq(11)
196+
expect(link.target_id).to eq(drink.id)
197+
expect(link[:target_type]).to eq(13)
198+
end
199+
end
152200
end

0 commit comments

Comments
 (0)