dbview_cti 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.1.1 (23/10/2013)
2
+
3
+ * Fixed handling of the remote part of associations (i.e. the belongs_to part when a cti-class has e.g. a has_many association)
4
+ * Blocks passed to e.g. has_many are no longer ignored
5
+
1
6
  ## 0.1.0 (22/10/2013)
2
7
 
3
8
  * Added transparent support for associations
@@ -0,0 +1,38 @@
1
+ require 'delegate'
2
+
3
+ module DBViewCTI
4
+ module Model
5
+
6
+ class CollectionDelegator < SimpleDelegator
7
+ def initialize(object, target_class_name)
8
+ super(object)
9
+ @target_class_name = target_class_name
10
+ end
11
+
12
+ def <<(object, *args, &block)
13
+ __getobj__.send('<<', object.convert_to(@target_class_name), *args, &block)
14
+ end
15
+
16
+ def []=(*args, &block)
17
+ object = args.last.convert_to(@target_class_name)
18
+ __getobj__.send('[]=', *(args[0..-2]), object, &block)
19
+ end
20
+
21
+ def delete(*args, &block)
22
+ objects = args.map do |obj|
23
+ obj.respond_to?(:convert_to) ? obj.convert_to(@target_class_name) : obj
24
+ end
25
+ __getobj__.send('delete', *objects, &block)
26
+ end
27
+
28
+ def destroy(*args, &block)
29
+ objects = args.map do |obj|
30
+ obj.respond_to?(:convert_to) ? obj.convert_to(@target_class_name) : obj
31
+ end
32
+ __getobj__.send('delete', *objects, &block)
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
@@ -35,7 +35,7 @@ module DBViewCTI
35
35
  type_string = type_string.camelize if type.is_a?(Symbol)
36
36
  return self if type_string == self.class.name
37
37
  query = self.class.cti_inner_join_sql(id, type_string)
38
- # query is nil when we try to cenvert to an descendant class (instead of an ascendant),
38
+ # query is nil when we try to cenvert to a descendant class (instead of an ascendant),
39
39
  # or when we try to convert to a class outside of the hierarchy
40
40
  if query.nil?
41
41
  specialized = specialize
@@ -117,7 +117,7 @@ module DBViewCTI
117
117
  # redefine association class methods (except for belongs_to)
118
118
  [:has_many, :has_and_belongs_to_many, :has_one].each do |name|
119
119
  self.class_eval <<-eos, __FILE__, __LINE__+1
120
- def #{name}(*args)
120
+ def #{name}(*args, &block)
121
121
  cti_initialize_cti_associations
122
122
  @cti_associations[:#{name}] << args.first
123
123
  super
@@ -125,8 +125,8 @@ module DBViewCTI
125
125
  eos
126
126
  end
127
127
 
128
- # redefine associations defined in ascendant classes so they keep working
129
128
  def cti_redefine_associations
129
+ # redefine associations defined in ascendant classes so they keep working
130
130
  @cti_ascendants.each do |ascendant|
131
131
  [:has_many, :has_and_belongs_to_many].each do |association_type|
132
132
  ascendant.constantize.cti_associations[association_type].each do |association|
@@ -158,8 +158,59 @@ module DBViewCTI
158
158
 
159
159
  def cti_redefine_single_association(name, class_name)
160
160
  self.class_eval <<-eos, __FILE__, __LINE__+1
161
- def #{name}(*args)
162
- self.convert_to('#{class_name}').send('#{name}', *args)
161
+ def #{name}(*args, &block)
162
+ self.convert_to('#{class_name}').send('#{name}', *args, &block)
163
+ end
164
+ eos
165
+ end
166
+
167
+ # fix the 'remote' (i.e. belongs_to) part of any has_one of has_many association in this class
168
+ def cti_redefine_remote_associations
169
+ cti_initialize_cti_associations
170
+ # redefine remote belongs_to associations
171
+ [:has_many, :has_one].each do |association_type|
172
+ @cti_associations[association_type].each do |association|
173
+ next if @cti_redefined_remote_associations[association_type].include?( association )
174
+ remote_class = association.to_s.camelize.singularize.constantize
175
+ remote_associations = remote_class.reflect_on_all_associations(:belongs_to).map(&:name)
176
+ remote_association = self.name.underscore.to_sym
177
+ if remote_associations.include?( remote_association )
178
+ cti_redefine_remote_belongs_to_association(remote_class, remote_association)
179
+ @cti_redefined_remote_associations[association_type] << association
180
+ end
181
+ end
182
+ end
183
+ # redefine remote has_many and has_and_belongs_to_many associations
184
+ [:has_many, :has_and_belongs_to_many].each do |association_type|
185
+ @cti_associations[association_type].each do |association|
186
+ next if @cti_redefined_remote_associations[association_type].include?( association )
187
+ remote_class = association.to_s.camelize.singularize.constantize
188
+ remote_associations = remote_class.reflect_on_all_associations( association_type ).map(&:name)
189
+ remote_association = self.name.underscore.pluralize.to_sym
190
+ if remote_associations.include?( remote_association )
191
+ cti_redefine_remote_to_many_association(remote_class, remote_association)
192
+ @cti_redefined_remote_associations[association_type] << association
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ def cti_redefine_remote_belongs_to_association(remote_class, remote_association)
199
+ remote_class.class_eval <<-eos, __FILE__, __LINE__+1
200
+ def #{remote_association}=(object, *args, &block)
201
+ super( object.convert_to('#{self.name}'), *args, &block )
202
+ end
203
+ eos
204
+ end
205
+
206
+ def cti_redefine_remote_to_many_association(remote_class, remote_association)
207
+ remote_class.class_eval <<-eos, __FILE__, __LINE__+1
208
+ def #{remote_association}=(objects, *args, &block)
209
+ super( objects.map { |o| o.convert_to('#{self.name}') }, *args, &block)
210
+ end
211
+ def #{remote_association}(*args, &block)
212
+ collection = super
213
+ DBViewCTI::Model::CollectionDelegator.new(collection, '#{self.name}')
163
214
  end
164
215
  eos
165
216
  end
@@ -180,8 +231,10 @@ module DBViewCTI
180
231
 
181
232
  def cti_initialize_cti_associations
182
233
  @cti_associations ||= {}
234
+ @cti_redefined_remote_associations ||= {}
183
235
  [:has_many, :has_and_belongs_to_many, :has_one].each do |name|
184
236
  @cti_associations[name] ||= []
237
+ @cti_redefined_remote_associations[name] ||= []
185
238
  end
186
239
  end
187
240
 
@@ -8,6 +8,7 @@ module DBViewCTI
8
8
  def cti_base_class
9
9
  self.class_eval { include(DBViewCTI::Model::CTI) }
10
10
  @cti_base_class = true
11
+ cti_redefine_remote_associations
11
12
  end
12
13
 
13
14
  def cti_derived_class
@@ -18,6 +19,10 @@ module DBViewCTI
18
19
  self.table_name = DBViewCTI::Names.view_name(self)
19
20
  self.superclass.cti_register_descendants(self.name)
20
21
  cti_redefine_associations
22
+ cti_redefine_remote_associations
23
+ # call redefine_remote_associations on superclass to deal with associations
24
+ # that were defined after the call to cti_derived_class or cti_base_class
25
+ self.superclass.cti_redefine_remote_associations
21
26
  end
22
27
 
23
28
  end
@@ -1,3 +1,3 @@
1
1
  module DBViewCTI
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/dbview_cti.rb CHANGED
@@ -28,6 +28,7 @@ module DBViewCTI
28
28
  extend ActiveSupport::Autoload
29
29
  autoload :CTI
30
30
  autoload :Extensions
31
+ autoload :CollectionDelegator
31
32
  end
32
33
  end
33
34
 
@@ -1,5 +1,5 @@
1
1
  class Launch < ActiveRecord::Base
2
2
  attr_accessible :spache_ship_id, :date unless Rails::VERSION::MAJOR > 3
3
3
 
4
- belongs_to :launch
4
+ belongs_to :space_ship
5
5
  end
@@ -19,104 +19,155 @@ describe SpaceShuttle do
19
19
  shuttle.convert_to(:space_ship).reliability.should eq 100
20
20
  end
21
21
 
22
- it "can use has_many associations defined in ascendant classes" do
23
- # create dummy space ships to make sure the shuttle we'll cereate has a different database id than
24
- # its associated spaceship
25
- (1..2).map { SpaceShip.create }
26
- shuttle = SpaceShuttle.create(:name => 'Discovery', :reliability => 100)
27
- shuttle.id.should_not eq shuttle.convert_to(:space_ship).id
28
- # test has_many functionality
29
- launch1 = Launch.new(:date => Date.today)
30
- launch2 = Launch.new(:date => Date.tomorrow)
31
- launch3 = Launch.new(:date => Date.tomorrow)
32
- expect {
33
- shuttle.launches << launch1
34
- shuttle.save!
35
- }.to change(Launch, :count).by(1)
36
- expect {
37
- shuttle.launches = [ launch1, launch2 ]
38
- shuttle.save!
39
- }.to change(Launch, :count).by(1)
40
- shuttle.launch_ids.sort.should eq [ launch1.id, launch2.id ]
41
- launch3.save!
42
- shuttle.launch_ids = [ launch1.id, launch3.id ]
43
- shuttle.launch_ids.sort.should eq [ launch1.id, launch3.id ]
44
- end
22
+ context 'associations' do
23
+ before :each do
24
+ # create dummy space ships to make sure the shuttle we'll create has a different database id than
25
+ # its associated spaceship
26
+ (1..2).map { SpaceShip.create }
27
+ @shuttle = SpaceShuttle.create(:name => 'Discovery', :reliability => 100)
28
+ @shuttle.id.should_not eq @shuttle.convert_to(:space_ship).id
29
+ end
30
+
31
+ it "can use has_many associations defined in ascendant classes" do
32
+ launch1 = Launch.new(:date => Date.today)
33
+ launch2 = Launch.new(:date => Date.tomorrow)
34
+ launch3 = Launch.new(:date => Date.tomorrow)
35
+ expect {
36
+ @shuttle.launches << launch1
37
+ @shuttle.save!
38
+ }.to change(Launch, :count).by(1)
39
+ expect {
40
+ @shuttle.launches = [ launch1, launch2 ]
41
+ @shuttle.save!
42
+ }.to change(Launch, :count).by(1)
43
+ @shuttle.launch_ids.sort.should eq [ launch1.id, launch2.id ]
44
+ launch3.save!
45
+ @shuttle.launch_ids = [ launch1.id, launch3.id ]
46
+ @shuttle.launch_ids.sort.should eq [ launch1.id, launch3.id ]
47
+ end
45
48
 
46
- it "can use has_many :through associations defined in ascendant classes" do
47
- # create dummy space ships to make sure the shuttle we'll cereate has a different database id than
48
- # its associated spaceship
49
- (1..2).map { SpaceShip.create }
50
- shuttle = SpaceShuttle.create(:name => 'Discovery', :reliability => 100)
51
- shuttle.id.should_not eq shuttle.convert_to(:space_ship).id
52
- # test has_many functionality
53
- experiment1 = Experiment.new(:name => 'Zero-gravity')
54
- experiment2 = Experiment.new(:name => 'Physics 101')
55
- experiment3 = Experiment.new(:name => 'Cell growth')
56
- expect {
57
- shuttle.experiments << experiment1
58
- shuttle.save!
59
- }.to change(Experiment, :count).by(1)
60
- expect {
61
- shuttle.experiments = [ experiment1, experiment2 ]
62
- shuttle.save!
63
- }.to change(Experiment, :count).by(1)
64
- shuttle.experiment_ids.sort.should eq [ experiment1.id, experiment2.id ]
65
- experiment3.save!
66
- shuttle.experiment_ids = [ experiment1.id, experiment3.id ]
67
- shuttle.experiment_ids.sort.should eq [ experiment1.id, experiment3.id ]
68
- Experiment.last.space_ships.first.specialize.id.should eq shuttle.id
69
- end
49
+ it "supports assignment on the 'remote' side of a has_many association" do
50
+ launch = Launch.new(:date => Date.today)
51
+ expect {
52
+ launch.space_ship = @shuttle
53
+ launch.save!
54
+ }.to change(Launch, :count).by(1)
55
+ launch.destroy
56
+ launch = Launch.new(:date => Date.today)
57
+ expect {
58
+ launch.space_ship = @shuttle.convert_to(:vehicle)
59
+ launch.save!
60
+ }.to change(Launch, :count).by(1)
61
+ end
62
+
63
+ it "can use has_many :through associations defined in ascendant classes" do
64
+ experiment1 = Experiment.new(:name => 'Zero-gravity')
65
+ experiment2 = Experiment.new(:name => 'Physics 101')
66
+ experiment3 = Experiment.new(:name => 'Cell growth')
67
+ expect {
68
+ @shuttle.experiments << experiment1
69
+ @shuttle.save!
70
+ }.to change(Experiment, :count).by(1)
71
+ expect {
72
+ @shuttle.experiments = [ experiment1, experiment2 ]
73
+ @shuttle.save!
74
+ }.to change(Experiment, :count).by(1)
75
+ @shuttle.experiment_ids.sort.should eq [ experiment1.id, experiment2.id ]
76
+ experiment3.save!
77
+ @shuttle.experiment_ids = [ experiment1.id, experiment3.id ]
78
+ @shuttle.experiment_ids.sort.should eq [ experiment1.id, experiment3.id ]
79
+ Experiment.last.space_ships.first.specialize.id.should eq @shuttle.id
80
+ end
70
81
 
71
- it "can use has_one associations defined in ascendant classes" do
72
- # create dummy space ships to make sure the shuttle we'll cereate has a different database id than
73
- # its associated captain
74
- (1..2).map { SpaceShip.create }
75
- shuttle = SpaceShuttle.create(:name => 'Discovery', :reliability => 100)
76
- shuttle.id.should_not eq shuttle.convert_to(:space_ship).id
77
- # test has_one functionality
78
- captain = Captain.new(:name => 'Armstrong')
79
- expect {
80
- shuttle.captain = captain
81
- shuttle.save!
82
- }.to change(Captain, :count).by(1)
83
- shuttle.reload
84
- shuttle.captain.id.should eq captain.id
85
- shuttle.captain.destroy
86
- expect {
87
- shuttle.create_captain(:name => 'Glenn')
88
- }.to change(Captain, :count).by(1)
89
- shuttle.captain.space_ship_id.should eq shuttle.convert_to(:space_ship).id
90
- expect {
91
- cap = shuttle.build_captain(:name => 'Aldrinn')
92
- cap.save!
93
- }.to change(Captain, :count).by(1)
94
- shuttle.captain.space_ship_id.should eq shuttle.convert_to(:space_ship).id
95
- end
82
+ it "supports operations on the 'remote' side of a has_many :through association" do
83
+ experiment = Experiment.new(:name => 'Zero-gravity')
84
+ shuttle2 = SpaceShuttle.create(:name => 'Endeavour', :reliability => 100)
85
+ shuttle2.id.should_not eq shuttle2.convert_to(:space_ship).id
86
+ expect {
87
+ experiment.space_ships = [@shuttle, shuttle2]
88
+ experiment.save!
89
+ }.to change(ExperimentSpaceShipPerformance, :count).by(2)
90
+ ExperimentSpaceShipPerformance.all.map(&:destroy)
91
+ expect {
92
+ experiment.space_ships << @shuttle
93
+ experiment.save!
94
+ }.to change(ExperimentSpaceShipPerformance, :count).by(1)
95
+ expect {
96
+ experiment.space_ships.delete(@shuttle)
97
+ }.to change(ExperimentSpaceShipPerformance, :count).by(-1)
98
+ end
99
+
100
+ it "can use has_one associations defined in ascendant classes" do
101
+ captain = Captain.new(:name => 'Armstrong')
102
+ expect {
103
+ @shuttle.captain = captain
104
+ @shuttle.save!
105
+ }.to change(Captain, :count).by(1)
106
+ @shuttle.reload
107
+ @shuttle.captain.id.should eq captain.id
108
+ @shuttle.captain.destroy
109
+ expect {
110
+ @shuttle.create_captain(:name => 'Glenn')
111
+ }.to change(Captain, :count).by(1)
112
+ @shuttle.captain.space_ship_id.should eq @shuttle.convert_to(:space_ship).id
113
+ expect {
114
+ cap = @shuttle.build_captain(:name => 'Aldrinn')
115
+ cap.save!
116
+ }.to change(Captain, :count).by(1)
117
+ @shuttle.captain.space_ship_id.should eq @shuttle.convert_to(:space_ship).id
118
+ end
96
119
 
97
- it "can use has_and_belongs_to_many associations defined in ascendant classes" do
98
- # create dummy space ships to make sure the shuttle we'll cereate has a different database id than
99
- # its associated spaceship
100
- (1..2).map { SpaceShip.create }
101
- shuttle = SpaceShuttle.create(:name => 'Discovery', :reliability => 100)
102
- shuttle.id.should_not eq shuttle.convert_to(:space_ship).id
103
- # test has_and_belongs_to_many functionality
104
- astronaut1 = Astronaut.new(:name => 'Armstrong')
105
- astronaut2 = Astronaut.new(:name => 'Glenn')
106
- astronaut3 = Astronaut.new(:name => 'Gagarin')
107
- expect {
108
- shuttle.astronauts << astronaut1
109
- shuttle.save!
110
- }.to change(Astronaut, :count).by(1)
111
- shuttle.astronauts.first.name.should eq astronaut1.name
112
- expect {
113
- shuttle.astronauts = [ astronaut1, astronaut2 ]
114
- shuttle.save!
115
- }.to change(Astronaut, :count).by(1)
116
- shuttle.astronaut_ids.sort.should eq [ astronaut1.id, astronaut2.id ]
117
- astronaut3.save!
118
- shuttle.astronaut_ids = [ astronaut1.id, astronaut3.id ]
119
- shuttle.astronaut_ids.sort.should eq [ astronaut1.id, astronaut3.id ]
120
+ it "supports operations on the 'remote' side of a has_one association" do
121
+ captain = Captain.new(:name => 'Armstrong')
122
+ expect {
123
+ captain.space_ship = @shuttle
124
+ captain.save!
125
+ }.to change(Captain, :count).by(1)
126
+ captain.destroy
127
+ captain = Captain.new(:name => 'Armstrong')
128
+ expect {
129
+ captain.space_ship = @shuttle.convert_to(:vehicle)
130
+ captain.save!
131
+ }.to change(Captain, :count).by(1)
132
+ end
133
+
134
+ it "can use has_and_belongs_to_many associations defined in ascendant classes" do
135
+ astronaut1 = Astronaut.new(:name => 'Armstrong')
136
+ astronaut2 = Astronaut.new(:name => 'Glenn')
137
+ astronaut3 = Astronaut.new(:name => 'Gagarin')
138
+ expect {
139
+ @shuttle.astronauts << astronaut1
140
+ @shuttle.save!
141
+ }.to change(Astronaut, :count).by(1)
142
+ @shuttle.astronauts.first.name.should eq astronaut1.name
143
+ expect {
144
+ @shuttle.astronauts = [ astronaut1, astronaut2 ]
145
+ @shuttle.save!
146
+ }.to change(Astronaut, :count).by(1)
147
+ @shuttle.astronaut_ids.sort.should eq [ astronaut1.id, astronaut2.id ]
148
+ astronaut3.save!
149
+ @shuttle.astronaut_ids = [ astronaut1.id, astronaut3.id ]
150
+ @shuttle.astronaut_ids.sort.should eq [ astronaut1.id, astronaut3.id ]
151
+ end
152
+
153
+ it "supports operations on the 'remote' side of a has_and_belongs_to_many association" do
154
+ astronaut = Astronaut.new(:name => 'Armstrong')
155
+ shuttle2 = SpaceShuttle.create(:name => 'Endeavour', :reliability => 100)
156
+ shuttle2.id.should_not eq shuttle2.convert_to(:space_ship).id
157
+ query = 'SELECT COUNT(*) FROM astronauts_space_ships'
158
+ ActiveRecord::Base.connection().execute(query)[0]['count'].to_i.should be_zero
159
+ astronaut.space_ships = [@shuttle, shuttle2]
160
+ astronaut.save!
161
+ ActiveRecord::Base.connection().execute(query)[0]['count'].to_i.should eq 2
162
+ astronaut.space_ships.delete(@shuttle, shuttle2)
163
+ ActiveRecord::Base.connection().execute(query)[0]['count'].to_i.should be_zero
164
+ astronaut.space_ships << @shuttle
165
+ astronaut.save!
166
+ ActiveRecord::Base.connection().execute(query)[0]['count'].to_i.should eq 1
167
+ astronaut.space_ships.destroy(@shuttle)
168
+ ActiveRecord::Base.connection().execute(query)[0]['count'].to_i.should be_zero
169
+ end
170
+
120
171
  end
121
-
172
+
122
173
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbview_cti
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.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-10-22 00:00:00.000000000 Z
12
+ date: 2013-10-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -78,6 +78,7 @@ files:
78
78
  - lib/db_view_cti/connection_adapters/schema_statements.rb
79
79
  - lib/db_view_cti/loader.rb
80
80
  - lib/db_view_cti/migration/command_recorder.rb
81
+ - lib/db_view_cti/model/collection_delegator.rb
81
82
  - lib/db_view_cti/model/cti.rb
82
83
  - lib/db_view_cti/model/extensions.rb
83
84
  - lib/db_view_cti/names.rb
@@ -210,7 +211,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
210
211
  version: '0'
211
212
  segments:
212
213
  - 0
213
- hash: -2732780512702174148
214
+ hash: 3228810689469855643
214
215
  required_rubygems_version: !ruby/object:Gem::Requirement
215
216
  none: false
216
217
  requirements:
@@ -219,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
220
  version: '0'
220
221
  segments:
221
222
  - 0
222
- hash: -2732780512702174148
223
+ hash: 3228810689469855643
223
224
  requirements: []
224
225
  rubyforge_project:
225
226
  rubygems_version: 1.8.25