polymorpheus 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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