polymorpheus 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,14 +67,21 @@ module Polymorpheus
67
67
 
68
68
  class_name = options[:class_name] || association.to_s.classify
69
69
 
70
- options[:conditions] = proc {
70
+ options[:conditions] = proc do
71
71
  keys = class_name.constantize
72
72
  .const_get('POLYMORPHEUS_ASSOCIATIONS')
73
73
  .map(&:foreign_key)
74
74
  keys.delete(fkey)
75
75
 
76
- keys.reduce({}) { |hash, key| hash.merge!(key => nil) }
77
- }
76
+ nil_columns = keys.reduce({}) { |hash, key| hash.merge!(key => nil) }
77
+
78
+
79
+ if self.is_a?(ActiveRecord::Associations::JoinDependency::JoinAssociation)
80
+ { aliased_table_name => nil_columns }
81
+ else
82
+ { association => nil_columns }
83
+ end
84
+ end
78
85
 
79
86
  has_many association, options
80
87
  end
@@ -1,3 +1,3 @@
1
1
  module Polymorpheus
2
- VERSION = '2.0.0'
2
+ VERSION = '2.0.1'
3
3
  end
@@ -1,201 +1,200 @@
1
1
  require 'active_record'
2
2
  require 'polymorpheus'
3
3
  require 'spec_helper'
4
+ require 'support/class_defs'
4
5
 
5
- # this is normally done via a Railtie in non-testing situations
6
- ActiveRecord::Base.send :include, Polymorpheus::Interface
6
+ describe '.belongs_to_polymorph' do
7
+ let(:hero) { Hero.create! }
8
+ let(:villain) { Villain.create! }
9
+ let(:superhero) { Superhero.create! }
10
+ let(:alien_demigod) { AlienDemigod.create! }
7
11
 
8
- class Shoe < ActiveRecord::Base
9
- belongs_to_polymorphic :man, :woman, :as => :wearer
10
- validates_polymorph :wearer
11
- end
12
-
13
- class Man < ActiveRecord::Base
14
- has_many_as_polymorph :shoes
15
- end
16
-
17
- class Woman < ActiveRecord::Base
18
- has_many_as_polymorph :shoes, order: 'id DESC'
19
- end
20
-
21
- class Dog < ActiveRecord::Base
22
- end
23
-
24
- class Glove < ActiveRecord::Base
25
- belongs_to_polymorphic :gentleman, :gentlewoman, :as => :wearer
26
- validates_polymorph :wearer
27
- end
28
-
29
- class Gentleman < Man
30
- end
31
-
32
- class Knight < Gentleman
33
- end
34
-
35
- class Gentlewoman < Woman
36
- end
37
-
38
- describe '.belongs_to_polymorphic' do
39
- it 'sets conditions on association to ensure we retrieve correct result' do
40
- man = Man.create!
41
- man.shoes.to_sql.squish
42
- .should == %{SELECT `shoes`.* FROM `shoes`
43
- WHERE `shoes`.`man_id` = 1
44
- AND `shoes`.`woman_id` IS NULL}.squish
45
- end
46
-
47
- it 'supports existing conditions on the association' do
48
- woman = Woman.create!
49
- woman.shoes.to_sql.squish
50
- .should == %{SELECT `shoes`.* FROM `shoes`
51
- WHERE `shoes`.`woman_id` = 1
52
- AND `shoes`.`man_id` IS NULL
53
- ORDER BY id DESC}.squish
54
- end
55
-
56
- it 'returns the correct result when used with new records' do
57
- woman = Woman.create!
58
- shoe = Shoe.create!(woman: woman, other_id: 10)
59
- Man.new.shoes.where(other_id: 10).should == []
60
- end
61
- end
62
-
63
- describe "polymorphic interface" do
64
-
65
- let(:man) { Man.create! }
66
- let(:woman) { Woman.create! }
67
- let(:gentleman) { Gentleman.create! }
68
- let(:knight) { Knight.create! }
69
-
70
- specify { Shoe::POLYMORPHEUS_ASSOCIATIONS.should == %w[man woman] }
71
- specify { Glove::POLYMORPHEUS_ASSOCIATIONS.should == %w[gentleman
72
- gentlewoman] }
12
+ specify { StoryArc::POLYMORPHEUS_ASSOCIATIONS.should == %w[hero villain] }
13
+ specify { Superpower::POLYMORPHEUS_ASSOCIATIONS.should == %w[superhero
14
+ supervillain] }
73
15
 
74
16
  describe "setter methods for ActiveRecord objects" do
75
- let(:shoe) { Shoe.new(attributes) }
17
+ let(:story_arc) { StoryArc.new(attributes) }
76
18
  let(:attributes) { {} }
77
19
 
78
20
  it "sets the correct attribute value for the setter" do
79
- shoe.wearer = man
80
- shoe.man_id.should == man.id
81
- shoe.woman_id.should == nil
21
+ story_arc.character = hero
22
+ story_arc.hero_id.should == hero.id
23
+ story_arc.villain_id.should == nil
82
24
  end
83
25
 
84
26
  it "sets competing associations to nil" do
85
- shoe.wearer = man
86
- shoe.man_id.should == man.id
87
- shoe.wearer = woman
88
- shoe.woman_id.should == woman.id
89
- shoe.man_id.should == nil
27
+ story_arc.character = hero
28
+ story_arc.hero_id.should == hero.id
29
+ story_arc.character = villain
30
+ story_arc.villain_id.should == villain.id
31
+ story_arc.hero_id.should == nil
90
32
  end
91
33
 
92
34
  it "throws an error if the assigned object isn't a valid type" do
93
- dog = Dog.create!
94
- expect { shoe.wearer = dog }
35
+ tree = Tree.create!
36
+ expect { story_arc.character = tree }
95
37
  .to raise_error(Polymorpheus::Interface::InvalidTypeError,
96
- "Invalid type. Must be one of {man, woman}")
38
+ "Invalid type. Must be one of {hero, villain}")
97
39
  end
98
40
 
99
41
  it "does not throw an error if the assigned object is a subclass of a
100
42
  valid type" do
101
- expect { shoe.wearer = gentleman }.not_to raise_error
102
- shoe.man_id.should == gentleman.id
43
+ expect { story_arc.character = superhero }.not_to raise_error
44
+ story_arc.hero_id.should == superhero.id
103
45
  end
104
46
 
105
47
  it "does not throw an error if the assigned object is a descendant of a
106
48
  valid type" do
107
- expect { shoe.wearer = knight }.not_to raise_error
108
- shoe.man_id.should == knight.id
49
+ expect { story_arc.character = alien_demigod }.not_to raise_error
50
+ story_arc.hero_id.should == alien_demigod.id
109
51
  end
110
52
  end
111
53
 
112
54
  describe "setter methods for objects inheriting from ActiveRecord objects" do
113
- let(:glove) { Glove.new }
55
+ let(:superpower) { Superpower.new }
114
56
 
115
57
  it "throws an error if the assigned object is an instance of the parent
116
58
  ActiveRecord class" do
117
- expect { glove.wearer = man }.to raise_error(
59
+ expect { superpower.wielder = hero }.to raise_error(
118
60
  Polymorpheus::Interface::InvalidTypeError,
119
- "Invalid type. Must be one of {gentleman, gentlewoman}"
61
+ "Invalid type. Must be one of {superhero, supervillain}"
120
62
  )
121
63
  end
122
64
 
123
65
  it "works if the assigned object is of the specified class" do
124
- expect { glove.wearer = gentleman }.not_to raise_error
125
- glove.gentleman_id.should == gentleman.id
66
+ expect { superpower.wielder = superhero }.not_to raise_error
67
+ superpower.superhero_id.should == superhero.id
126
68
  end
127
69
 
128
70
  it "works if the assigned object is an instance of a child class" do
129
- expect { glove.wearer = knight }.not_to raise_error
130
- glove.gentleman_id.should == knight.id
131
- end
132
- end
133
-
134
- describe '.validates_polymorph validation' do
135
- specify { Shoe.new(wearer: man).valid?.should == true }
136
- specify { Shoe.new(wearer: woman).valid?.should == true }
137
- specify { Shoe.new(man_id: man.id).valid?.should == true }
138
- specify { Shoe.new(man: man).valid?.should == true }
139
- specify { Shoe.new(man: Man.new).valid?.should == true }
140
-
141
- it 'is invalid if no association is specified' do
142
- shoe = Shoe.new
143
- shoe.valid?.should == false
144
- shoe.errors[:base].should ==
145
- ["You must specify exactly one of the following: {man, woman}"]
146
- end
147
-
148
- it 'is invalid if multiple associations are specified' do
149
- shoe = Shoe.new(man_id: man.id, woman_id: woman.id)
150
- shoe.valid?.should == false
151
- shoe.errors[:base].should ==
152
- ["You must specify exactly one of the following: {man, woman}"]
71
+ expect { superpower.wielder = alien_demigod }.not_to raise_error
72
+ superpower.superhero_id.should == alien_demigod.id
153
73
  end
154
74
  end
155
75
 
156
76
  describe '#polymorpheus exposed interface method' do
157
- subject(:interface) { shoe.polymorpheus }
77
+ subject(:interface) { story_arc.polymorpheus }
158
78
 
159
79
  context 'when there is no relationship defined' do
160
- let(:shoe) { Shoe.new }
80
+ let(:story_arc) { StoryArc.new }
161
81
 
162
- its(:associations) { should match_associations(:man, :woman) }
82
+ its(:associations) { should match_associations(:hero, :villain) }
163
83
  its(:active_association) { should == nil }
164
84
  its(:query_condition) { should == nil }
165
85
  end
166
86
 
167
87
  context 'when there is are multiple relationships defined' do
168
- let(:shoe) { Shoe.new(man_id: man.id, woman_id: woman.id) }
88
+ let(:story_arc) { StoryArc.new(hero_id: hero.id, villain_id: villain.id) }
169
89
 
170
- its(:associations) { should match_associations(:man, :woman) }
90
+ its(:associations) { should match_associations(:hero, :villain) }
171
91
  its(:active_association) { should == nil }
172
92
  its(:query_condition) { should == nil }
173
93
  end
174
94
 
175
95
  context 'when there is one relationship defined through the id value' do
176
- let(:shoe) { Shoe.new(man_id: man.id) }
96
+ let(:story_arc) { StoryArc.new(hero_id: hero.id) }
177
97
 
178
- its(:associations) { should match_associations(:man, :woman) }
179
- its(:active_association) { be_association(:man) }
180
- its(:query_condition) { should == { 'man_id' => man.id } }
98
+ its(:associations) { should match_associations(:hero, :villain) }
99
+ its(:active_association) { be_association(:hero) }
100
+ its(:query_condition) { should == { 'hero_id' => hero.id } }
181
101
  end
182
102
 
183
103
  context 'when there is one relationship defined through the setter' do
184
- let(:shoe) { Shoe.new(wearer: man) }
104
+ let(:story_arc) { StoryArc.new(character: hero) }
185
105
 
186
- its(:associations) { should match_associations(:man, :woman) }
187
- its(:active_association) { be_association(:man) }
188
- its(:query_condition) { should == { 'man_id' => man.id } }
106
+ its(:associations) { should match_associations(:hero, :villain) }
107
+ its(:active_association) { be_association(:hero) }
108
+ its(:query_condition) { should == { 'hero_id' => hero.id } }
189
109
  end
190
110
 
191
111
  context 'when there is one association, to a new record' do
192
- let(:new_man) { Man.new }
193
- let(:shoe) { Shoe.new(wearer: new_man) }
112
+ let(:new_hero) { Hero.new }
113
+ let(:story_arc) { StoryArc.new(character: new_hero) }
194
114
 
195
- its(:associations) { should match_associations(:man, :woman) }
196
- its(:active_association) { be_association(:man) }
115
+ its(:associations) { should match_associations(:hero, :villain) }
116
+ its(:active_association) { be_association(:hero) }
197
117
  its(:query_condition) { should == nil }
198
118
  end
199
119
  end
120
+ end
121
+
122
+ describe '.has_many_as_polymorph' do
123
+ it 'sets conditions on association to ensure we retrieve correct result' do
124
+ hero = Hero.create!
125
+ hero.story_arcs.to_sql
126
+ .should match_sql(%{SELECT `story_arcs`.* FROM `story_arcs`
127
+ WHERE `story_arcs`.`hero_id` = #{hero.id}
128
+ AND `story_arcs`.`villain_id` IS NULL})
129
+ end
200
130
 
131
+ it 'supports existing conditions on the association' do
132
+ villain = Villain.create!
133
+ villain.story_arcs.to_sql
134
+ .should match_sql(%{SELECT `story_arcs`.* FROM `story_arcs`
135
+ WHERE `story_arcs`.`villain_id` = #{villain.id}
136
+ AND `story_arcs`.`hero_id` IS NULL
137
+ ORDER BY id DESC})
138
+ end
139
+
140
+ it 'returns the correct result when used with new records' do
141
+ villain = Villain.create!
142
+ story_arc = StoryArc.create!(villain: villain, issue_id: 10)
143
+ Hero.new.story_arcs.where(issue_id: 10).should == []
144
+ end
145
+
146
+ it 'sets conditions on associations with enough specificity that they work
147
+ in conjunction with has_many :through relationships' do
148
+ hero = Hero.create!
149
+ hero.battles.to_sql
150
+ .should match_sql(%{SELECT `battles`.* FROM `battles`
151
+ INNER JOIN `story_arcs`
152
+ ON `battles`.`id` = `story_arcs`.`battle_id`
153
+ WHERE `story_arcs`.`hero_id` = 16
154
+ AND `story_arcs`.`villain_id` IS NULL})
155
+ end
156
+
157
+ it 'uses the correct association table name when used in conjunction with a
158
+ join condition' do
159
+ battle = Battle.create!
160
+ battle.heros.to_sql
161
+ .should match_sql(%{SELECT `heros`.* FROM `heros`
162
+ INNER JOIN `story_arcs`
163
+ ON `heros`.`id` = `story_arcs`.`hero_id`
164
+ WHERE `story_arcs`.`battle_id` = #{battle.id}})
165
+
166
+ battle.heros.joins(:story_arcs).to_sql
167
+ .should match_sql(%{SELECT `heros`.* FROM `heros`
168
+ INNER JOIN `story_arcs` `story_arcs_heros`
169
+ ON `story_arcs_heros`.`hero_id` = `heros`.`id`
170
+ AND `story_arcs_heros`.`villain_id` IS NULL
171
+ INNER JOIN `story_arcs`
172
+ ON `heros`.`id` = `story_arcs`.`hero_id`
173
+ WHERE `story_arcs`.`battle_id` = #{battle.id}})
174
+ end
175
+ end
176
+
177
+ describe '.validates_polymorph' do
178
+ let(:hero) { Hero.create! }
179
+ let(:villain) { Villain.create! }
180
+
181
+ specify { StoryArc.new(character: hero).valid?.should == true }
182
+ specify { StoryArc.new(character: villain).valid?.should == true }
183
+ specify { StoryArc.new(hero_id: hero.id).valid?.should == true }
184
+ specify { StoryArc.new(hero: hero).valid?.should == true }
185
+ specify { StoryArc.new(hero: Hero.new).valid?.should == true }
186
+
187
+ it 'is invalid if no association is specified' do
188
+ story_arc = StoryArc.new
189
+ story_arc.valid?.should == false
190
+ story_arc.errors[:base].should ==
191
+ ["You must specify exactly one of the following: {hero, villain}"]
192
+ end
193
+
194
+ it 'is invalid if multiple associations are specified' do
195
+ story_arc = StoryArc.new(hero_id: hero.id, villain_id: villain.id)
196
+ story_arc.valid?.should == false
197
+ story_arc.errors[:base].should ==
198
+ ["You must specify exactly one of the following: {hero, villain}"]
199
+ end
201
200
  end
@@ -1,45 +1,2 @@
1
- ActiveRecord::Base.establish_connection({
2
- adapter: 'mysql2',
3
- username: 'root',
4
- password: '',
5
- host: 'localhost',
6
- database: 'polymorphicTest'
7
- })
8
-
9
- ActiveRecord::Base.connection.tables.each do |table|
10
- ActiveRecord::Base.connection.drop_table table
11
- end
12
-
13
- ActiveRecord::Schema.define do
14
- create_table :shoes do |t|
15
- t.integer :man_id
16
- t.integer :woman_id
17
- t.integer :other_id
18
- end
19
- create_table :gloves do |t|
20
- t.integer :gentleman_id
21
- t.integer :gentlewoman_id
22
- end
23
- create_table :men do |t|
24
- t.string :type
25
- end
26
- create_table :women
27
- create_table :dogs
28
- end
29
-
30
-
31
- RSpec::Matchers.define :be_association do |association_name|
32
- match do |actual|
33
- actual.should be_instance_of(Polymorpheus::InterfaceBuilder::Association)
34
- actual.name.should == association_name.to_s
35
- end
36
- end
37
-
38
- RSpec::Matchers.define :match_associations do |*association_names|
39
- match do |actual|
40
- actual.length.should == association_names.length
41
- actual.each_with_index do |item, ind|
42
- item.should be_association(association_names[ind])
43
- end
44
- end
45
- end
1
+ require 'support/custom_matchers.rb'
2
+ require 'support/db_setup.rb'
@@ -0,0 +1,48 @@
1
+ # this is normally done via a Railtie in non-testing situations
2
+ ActiveRecord::Base.send :include, Polymorpheus::Interface
3
+
4
+ # Han Solo is a hero, but not a superhero
5
+ class Hero < ActiveRecord::Base
6
+ has_many_as_polymorph :story_arcs
7
+ has_many :battles, through: :story_arcs
8
+ end
9
+
10
+ # Hannibal Lecter is a villain, but not a supervillain
11
+ class Villain < ActiveRecord::Base
12
+ has_many_as_polymorph :story_arcs, order: 'id DESC'
13
+ has_many :battles, through: :story_arcs
14
+ end
15
+
16
+ # Flash is a superhero but not an alien demigod
17
+ class Superhero < Hero
18
+ end
19
+
20
+ # Superman is an alien demigod
21
+ class AlienDemigod < Superhero
22
+ end
23
+
24
+ # Darkseid is a supervillain
25
+ class Supervillain < Villain
26
+ end
27
+
28
+ # All heros and villains have story arcs
29
+ class StoryArc < ActiveRecord::Base
30
+ belongs_to_polymorphic :hero, :villain, as: :character
31
+ belongs_to :battle
32
+ validates_polymorph :character
33
+ end
34
+
35
+ class Battle < ActiveRecord::Base
36
+ has_many :story_arcs
37
+ has_many :heros, through: :story_arcs
38
+ end
39
+
40
+ # But only super-people have superpowers
41
+ class Superpower < ActiveRecord::Base
42
+ belongs_to_polymorphic :superhero, :supervillain, as: :wielder
43
+ end
44
+
45
+ # Trees, though, are masters of zen. They sway with the wind.
46
+ # (Unless this is LOTR, but let's ignore that for now.)
47
+ class Tree < ActiveRecord::Base
48
+ end
@@ -0,0 +1,31 @@
1
+ RSpec::Matchers.define :be_association do |association_name|
2
+ match do |actual|
3
+ actual.should be_instance_of(Polymorpheus::InterfaceBuilder::Association)
4
+ actual.name.should == association_name.to_s
5
+ end
6
+ end
7
+
8
+ RSpec::Matchers.define :match_associations do |*association_names|
9
+ match do |actual|
10
+ actual.length.should == association_names.length
11
+ actual.each_with_index do |item, ind|
12
+ item.should be_association(association_names[ind])
13
+ end
14
+ end
15
+ end
16
+
17
+ RSpec::Matchers.define :match_sql do |expected|
18
+ match do |actual|
19
+ format(expected).should == format(actual)
20
+ end
21
+
22
+ failure_message_for_should do |actual|
23
+ "expected the following SQL statements to match:
24
+ #{format(actual)}
25
+ #{format(expected)}"
26
+ end
27
+
28
+ def format(sql)
29
+ sql.gsub(/\s+/, ' ')
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ ActiveRecord::Base.establish_connection({
2
+ adapter: 'mysql2',
3
+ username: 'travis',
4
+ database: 'polymorpheus_test'
5
+ })
6
+
7
+ ActiveRecord::Base.connection.tables.each do |table|
8
+ ActiveRecord::Base.connection.drop_table table
9
+ end
10
+
11
+ ActiveRecord::Schema.define do
12
+ create_table :heros
13
+ create_table :villains
14
+ create_table :superheros
15
+ create_table :alien_demigods
16
+ create_table :supervillains
17
+ create_table :trees
18
+
19
+ create_table :story_arcs do |t|
20
+ t.integer :hero_id
21
+ t.integer :villain_id
22
+ t.integer :battle_id
23
+ t.integer :issue_id
24
+ end
25
+
26
+ create_table :battles
27
+
28
+ create_table :superpowers do |t|
29
+ t.integer :superhero_id
30
+ t.integer :supervillain_id
31
+ end
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polymorpheus
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-09-19 00:00:00.000000000 Z
12
+ date: 2013-11-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: foreigner
@@ -100,6 +100,9 @@ files:
100
100
  - spec/shared_examples.rb
101
101
  - spec/spec_helper.rb
102
102
  - spec/sql_logger.rb
103
+ - spec/support/class_defs.rb
104
+ - spec/support/custom_matchers.rb
105
+ - spec/support/db_setup.rb
103
106
  - spec/trigger_spec.rb
104
107
  - LICENSE.txt
105
108
  - README.md
@@ -126,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
129
  version: 1.3.6
127
130
  requirements: []
128
131
  rubyforge_project:
129
- rubygems_version: 1.8.25
132
+ rubygems_version: 1.8.23
130
133
  signing_key:
131
134
  specification_version: 3
132
135
  summary: Provides a database-friendly method for polymorphic relationships