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 +5 -0
- data/lib/db_view_cti/model/collection_delegator.rb +38 -0
- data/lib/db_view_cti/model/cti.rb +58 -5
- data/lib/db_view_cti/model/extensions.rb +5 -0
- data/lib/db_view_cti/version.rb +1 -1
- data/lib/dbview_cti.rb +1 -0
- data/spec/dummy-rails-3/app/models/launch.rb +1 -1
- data/spec/models/space_shuttle_spec.rb +147 -96
- metadata +5 -4
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
|
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
|
data/lib/db_view_cti/version.rb
CHANGED
data/lib/dbview_cti.rb
CHANGED
@@ -19,104 +19,155 @@ describe SpaceShuttle do
|
|
19
19
|
shuttle.convert_to(:space_ship).reliability.should eq 100
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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.
|
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-
|
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:
|
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:
|
223
|
+
hash: 3228810689469855643
|
223
224
|
requirements: []
|
224
225
|
rubyforge_project:
|
225
226
|
rubygems_version: 1.8.25
|