dbview_cti 0.1.0 → 0.1.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.
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