dbview_cti 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.1.2 (27/10/2013)
2
+
3
+ * Fundamental change to how associations are handled in order to solve problems with build
4
+ * Added support for accepts_nested_attributes_for
5
+ * Validation errors on associated models are now correctly handled
6
+
1
7
  ## 0.1.1 (23/10/2013)
2
8
 
3
9
  * 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)
data/README.md CHANGED
@@ -175,8 +175,29 @@ end
175
175
 
176
176
  ## Associations
177
177
 
178
- Associations (`has_many`, `has_one`, etc.) work and are inherited as you would expect. The only caveat is that
179
- in Rails 4 it might be necessary to explicitly specify the join table when using `has_and_belongs_to_many`.
178
+ Associations (`has_many`, `has_one`, etc.) work and are inherited as you would expect. There are two caveats:
179
+
180
+ * In the base class, you have to call `cti_base_class` before defining any associations:
181
+
182
+ ```ruby
183
+ class Vehicle < ActiveRecord::Base
184
+ # call cti_base_class first...
185
+ cti_base_class
186
+
187
+ # ...before defining any associations
188
+ has_many :parts
189
+ end
190
+ ```
191
+
192
+ * In Rails 4 it might be necessary to explicitly specify the join table when using `has_and_belongs_to_many`:
193
+
194
+ ```ruby
195
+ class SpaceShip < Vehicle
196
+ cti_derived_class
197
+
198
+ has_and_belongs_to_many :astronauts, :join_table => 'astronauts_space_ships'
199
+ end
200
+ ```
180
201
 
181
202
  ## API
182
203
 
@@ -31,6 +31,7 @@ module DBViewCTI
31
31
  end
32
32
 
33
33
  def convert_to(type)
34
+ return nil unless persisted?
34
35
  type_string = type.to_s
35
36
  type_string = type_string.camelize if type.is_a?(Symbol)
36
37
  return self if type_string == self.class.name
@@ -48,26 +49,73 @@ module DBViewCTI
48
49
  type_string.constantize.find(id.to_i)
49
50
  end
50
51
 
51
- # change destroy and delete methods to operate on most specialized obect
52
+ # change destroy and delete methods to operate on most specialized object
52
53
  included do
53
- alias_method_chain :destroy, :specialize
54
- alias_method_chain :delete, :specialize
54
+ alias_method_chain :destroy, :cti
55
+ alias_method_chain :delete, :cti
55
56
  # destroy! seems te be defined in Rails 4
56
- alias_method_chain :destroy!, :specialize if self.method_defined?(:destroy!)
57
+ alias_method_chain :destroy!, :cti if self.method_defined?(:destroy!)
58
+
59
+ # for associations:
60
+ alias_method_chain :association, :cti
61
+ validate :cti_validate_associations
62
+
63
+ # save callbacks (necessary for saving associations)
64
+ after_save :cti_save_associations
57
65
  end
58
66
 
59
- def destroy_with_specialize
60
- specialize.destroy_without_specialize
67
+ def destroy_with_cti
68
+ specialize.destroy_without_cti
61
69
  end
62
70
 
63
- def destroy_with_specialize!
64
- specialize.destroy_without_specialize!
71
+ def destroy_with_cti!
72
+ specialize.destroy_without_cti!
65
73
  end
66
74
 
67
- def delete_with_specialize
68
- specialize.delete_without_specialize
75
+ def delete_with_cti
76
+ specialize.delete_without_cti
77
+ end
78
+
79
+ def cti_validate_associations
80
+ return_value = true
81
+ self.class.cti_association_proxies.each_key do |proxy_name|
82
+ proxy = instance_variable_get(proxy_name)
83
+ if proxy && !proxy.valid?
84
+ errors.messages.merge!(proxy.errors.messages)
85
+ return_value = false
86
+ end
87
+ end
88
+ return_value
89
+ end
90
+
91
+ def cti_save_associations
92
+ self.class.cti_association_proxies.each_key do |proxy_name|
93
+ proxy = instance_variable_get(proxy_name)
94
+ proxy.save if proxy
95
+ end
96
+ true
69
97
  end
70
98
 
99
+ def association_with_cti(*args)
100
+ return association_without_cti(*args) unless args.length == 1
101
+ association_name = args[0]
102
+ proxy = cti_association_proxy(association_name)
103
+ proxy ||= self
104
+ proxy.association_without_cti(association_name)
105
+ end
106
+
107
+ def cti_association_proxy(association_name)
108
+ return nil if self.class.reflect_on_all_associations(:belongs_to).map(&:name).include?(association_name.to_sym)
109
+ proxy_name = self.class.cti_association_proxy_name(association_name)
110
+ proxy = instance_variable_get(proxy_name)
111
+ if !proxy && !self.class.cti_has_association?(association_name)
112
+ instance_variable_set(proxy_name,
113
+ ModelDelegator.new(self, self.class.cti_association_proxies[proxy_name]))
114
+ proxy = instance_variable_get(proxy_name)
115
+ end
116
+ proxy
117
+ end
118
+
71
119
  module ClassMethods
72
120
 
73
121
  def cti_base_class?
@@ -78,7 +126,7 @@ module DBViewCTI
78
126
  !!@cti_derived_class
79
127
  end
80
128
 
81
- attr_accessor :cti_descendants, :cti_ascendants
129
+ attr_accessor :cti_descendants, :cti_ascendants, :cti_association_proxies
82
130
 
83
131
  # registers a derived class and its descendants in the current class
84
132
  # class_name: name of derived class (the one calling cti_register_descendants on this class)
@@ -114,7 +162,7 @@ module DBViewCTI
114
162
  result
115
163
  end
116
164
 
117
- # redefine association class methods (except for belongs_to)
165
+ # redefine association class methods
118
166
  [:has_many, :has_and_belongs_to_many, :has_one].each do |name|
119
167
  self.class_eval <<-eos, __FILE__, __LINE__+1
120
168
  def #{name}(*args, &block)
@@ -125,45 +173,20 @@ module DBViewCTI
125
173
  eos
126
174
  end
127
175
 
128
- def cti_redefine_associations
129
- # redefine associations defined in ascendant classes so they keep working
176
+ def cti_create_association_proxies
177
+ # create hash with proxy and class names. The proxies themselves will be created
178
+ # by the 'association' instance method when the association is used for the first time.
179
+ @cti_association_proxies ||= {}
130
180
  @cti_ascendants.each do |ascendant|
131
- [:has_many, :has_and_belongs_to_many].each do |association_type|
181
+ [:has_many, :has_and_belongs_to_many, :has_one].each do |association_type|
132
182
  ascendant.constantize.cti_associations[association_type].each do |association|
133
- cti_redefine_to_many(ascendant, association)
183
+ proxy_name = cti_association_proxy_name(association)
184
+ @cti_association_proxies[proxy_name] = ascendant
134
185
  end
135
186
  end
136
- ascendant.constantize.cti_associations[:has_one].each do |association|
137
- cti_redefine_has_one(ascendant, association)
138
- end
139
187
  end
140
188
  end
141
189
 
142
- # redefine has_many and has_and_belongs_to_many association
143
- def cti_redefine_to_many(class_name, association)
144
- plural = association.to_s
145
- singular = association.to_s.singularize
146
- [ plural, "#{plural}=", "#{singular}_ids", "#{singular}_ids=" ].each do |name|
147
- cti_redefine_single_association(name, class_name)
148
- end
149
- end
150
-
151
- # redefine has_many and has_and_belongs_to_many association
152
- def cti_redefine_has_one(class_name, association)
153
- singular = association
154
- [ association, "#{association}=", "build_#{association}", "create_#{association}", "create_#{association}!" ].each do |name|
155
- cti_redefine_single_association(name, class_name)
156
- end
157
- end
158
-
159
- def cti_redefine_single_association(name, class_name)
160
- self.class_eval <<-eos, __FILE__, __LINE__+1
161
- def #{name}(*args, &block)
162
- self.convert_to('#{class_name}').send('#{name}', *args, &block)
163
- end
164
- eos
165
- end
166
-
167
190
  # fix the 'remote' (i.e. belongs_to) part of any has_one of has_many association in this class
168
191
  def cti_redefine_remote_associations
169
192
  cti_initialize_cti_associations
@@ -214,12 +237,26 @@ module DBViewCTI
214
237
  end
215
238
  eos
216
239
  end
240
+
241
+ def cti_association_proxy_name(association)
242
+ "@cti_#{association}_association_proxy"
243
+ end
217
244
 
218
245
  def cti_associations
219
246
  cti_initialize_cti_associations
220
247
  @cti_associations
221
248
  end
222
249
 
250
+ def cti_has_association?(association_name)
251
+ if !@cti_all_associations
252
+ @cti_all_associations = @cti_associations.keys.inject([]) do |result, key|
253
+ result += @cti_associations[key]
254
+ result
255
+ end
256
+ end
257
+ @cti_all_associations.include?(association_name.to_sym)
258
+ end
259
+
223
260
  include DBViewCTI::SQLGeneration::Model
224
261
 
225
262
  # this method is only used in testing. It returns the number of rows present in the real database
@@ -236,6 +273,7 @@ module DBViewCTI
236
273
  @cti_associations[name] ||= []
237
274
  @cti_redefined_remote_associations[name] ||= []
238
275
  end
276
+ @cti_association_proxies ||= {}
239
277
  end
240
278
 
241
279
  end
@@ -18,7 +18,7 @@ module DBViewCTI
18
18
  @cti_derived_class = true
19
19
  self.table_name = DBViewCTI::Names.view_name(self)
20
20
  self.superclass.cti_register_descendants(self.name)
21
- cti_redefine_associations
21
+ cti_create_association_proxies
22
22
  cti_redefine_remote_associations
23
23
  # call redefine_remote_associations on superclass to deal with associations
24
24
  # that were defined after the call to cti_derived_class or cti_base_class
@@ -0,0 +1,48 @@
1
+ require 'delegate'
2
+
3
+ module DBViewCTI
4
+ module Model
5
+
6
+ class ModelDelegator < SimpleDelegator
7
+
8
+ attr_reader :cti_target_class
9
+
10
+ def initialize(object, target_class)
11
+ @cti_object = object
12
+ @cti_converted_object = object.convert_to(target_class)
13
+ if !@cti_converted_object
14
+ @cti_converted_object = object.becomes(target_class.constantize)
15
+ @cti_is_new = true
16
+ end
17
+ @cti_target_class = target_class
18
+ super( @cti_converted_object )
19
+ end
20
+
21
+ def cti_is_new?
22
+ @cti_is_new
23
+ end
24
+
25
+ def save(*args, &block)
26
+ return super unless cti_is_new?
27
+ # special case for new objects, we need som hackish id-juggling
28
+ old_id = @cti_object.id
29
+ new_id = @cti_object.convert_to( @cti_target_class ).id
30
+ # since @cti_converted_object was created using 'becomes', @cti_object.id changes
31
+ # as well in the following statement. So we saved it in old_id and restore it after the
32
+ # call to save (i.e. super)
33
+ @cti_object.reload # only needed in rails 4
34
+ self.id = new_id
35
+ self.created_at = @cti_object.created_at # only needed in rails 4
36
+ self.updated_at = @cti_object.updated_at # only needed in rails 4
37
+ retval = !!super
38
+ @cti_is_new = false
39
+ @cti_object.id = old_id
40
+ @cti_converted_object = @cti_object.convert_to( @cti_target_class )
41
+ __setobj__(@cti_converted_object)
42
+ retval
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module DBViewCTI
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
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 :ModelDelegator
31
32
  autoload :CollectionDelegator
32
33
  end
33
34
  end
@@ -0,0 +1,5 @@
1
+ class Category < ActiveRecord::Base
2
+ attr_accessible :name unless Rails::VERSION::MAJOR > 3
3
+ has_many :space_ships
4
+ validates :name, :presence => true
5
+ end
@@ -2,4 +2,6 @@ class Launch < ActiveRecord::Base
2
2
  attr_accessible :spache_ship_id, :date unless Rails::VERSION::MAJOR > 3
3
3
 
4
4
  belongs_to :space_ship
5
+
6
+ validates :date, :presence => true
5
7
  end
@@ -2,9 +2,18 @@ class SpaceShip < Vehicle
2
2
  attr_accessible :single_use, :reliability unless Rails::VERSION::MAJOR > 3
3
3
  cti_derived_class
4
4
 
5
+ belongs_to :category
6
+
5
7
  has_many :launches
8
+ accepts_nested_attributes_for :launches
9
+
6
10
  has_one :captain
11
+ accepts_nested_attributes_for :captain
12
+
7
13
  has_and_belongs_to_many :astronauts, :join_table => 'astronauts_space_ships'
14
+ accepts_nested_attributes_for :astronauts
15
+
8
16
  has_many :experiment_space_ship_performances
9
17
  has_many :experiments, :through => :experiment_space_ship_performances
18
+ accepts_nested_attributes_for :experiments
10
19
  end
@@ -0,0 +1,9 @@
1
+ class CreateCategories < ActiveRecord::Migration
2
+ def change
3
+ create_table :categories do |t|
4
+ t.string :name
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -2,12 +2,15 @@ class CreateSpaceShips < ActiveRecord::Migration
2
2
  def change
3
3
  create_table :space_ships do |t|
4
4
  t.references :vehicle
5
+
6
+ t.references :category
5
7
  t.boolean :single_use
6
8
 
7
9
  t.timestamps
8
10
  end
9
11
 
10
12
  add_foreign_key(:space_ships, :vehicles)
13
+ add_foreign_key(:space_ships, :categories)
11
14
  cti_create_view('SpaceShip')
12
15
  end
13
16
  end
@@ -39,6 +39,12 @@ ActiveRecord::Schema.define(:version => 20131022030720) do
39
39
  t.datetime "updated_at", :null => false
40
40
  end
41
41
 
42
+ create_table "categories", :force => true do |t|
43
+ t.string "name"
44
+ t.datetime "created_at", :null => false
45
+ t.datetime "updated_at", :null => false
46
+ end
47
+
42
48
  create_table "experiment_space_ship_performances", :force => true do |t|
43
49
  t.integer "experiment_id"
44
50
  t.integer "space_ship_id"
@@ -84,6 +90,7 @@ ActiveRecord::Schema.define(:version => 20131022030720) do
84
90
 
85
91
  create_table "space_ships", :force => true do |t|
86
92
  t.integer "vehicle_id"
93
+ t.integer "category_id"
87
94
  t.boolean "single_use"
88
95
  t.datetime "created_at", :null => false
89
96
  t.datetime "updated_at", :null => false
@@ -122,6 +129,7 @@ ActiveRecord::Schema.define(:version => 20131022030720) do
122
129
 
123
130
  add_foreign_key "rocket_engines", "space_ships", :name => "rocket_engines_space_ship_id_fk"
124
131
 
132
+ add_foreign_key "space_ships", "categories", :name => "space_ships_category_id_fk"
125
133
  add_foreign_key "space_ships", "vehicles", :name => "space_ships_vehicle_id_fk"
126
134
 
127
135
  add_foreign_key "space_shuttles", "space_ships", :name => "space_shuttles_space_ship_id_fk"
@@ -44,6 +44,39 @@ describe SpaceShuttle do
44
44
  launch3.save!
45
45
  @shuttle.launch_ids = [ launch1.id, launch3.id ]
46
46
  @shuttle.launch_ids.sort.should eq [ launch1.id, launch3.id ]
47
+ # test build functionality for adding to existing collection
48
+ expect {
49
+ @shuttle.launches.build(:date => Date.yesterday)
50
+ @shuttle.save!
51
+ }.to change(Launch, :count).by(1)
52
+ @shuttle.launches.order(:id).last.date.should eq Date.yesterday
53
+ # test build functionality for new collection
54
+ @shuttle.launches.clear
55
+ @shuttle.launch_ids.should eq []
56
+ expect {
57
+ @shuttle.name = 'Shuttle'
58
+ @shuttle.launches.build(:date => Date.yesterday)
59
+ @shuttle.save!
60
+ }.to change(Launch, :count).by(1)
61
+ @shuttle.name.should eq 'Shuttle'
62
+ # test build functionality for new collection and new object
63
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
64
+ expect {
65
+ expect {
66
+ shuttle.name = 'Shuttle'
67
+ shuttle.launches.build(:date => Date.yesterday)
68
+ shuttle.launches.build(:date => Date.tomorrow)
69
+ shuttle.save!
70
+ }.to change(Launch, :count).by(2)
71
+ }.to change(SpaceShuttle, :count).by(1)
72
+ shuttle.reload
73
+ shuttle.name.should eq 'Shuttle'
74
+ # test adding onto existing collection
75
+ expect {
76
+ shuttle.launches.build(:date => Date.today)
77
+ shuttle.save!
78
+ }.to change(Launch, :count).by(1)
79
+ shuttle.launches.order(:id).last.date.should eq Date.today
47
80
  end
48
81
 
49
82
  it "supports assignment on the 'remote' side of a has_many association" do
@@ -59,6 +92,27 @@ describe SpaceShuttle do
59
92
  launch.save!
60
93
  }.to change(Launch, :count).by(1)
61
94
  end
95
+
96
+ it "supports accepts_nested_attributes for has_many associations defined in ascendant classes" do
97
+ expect {
98
+ @shuttle.launches_attributes = [
99
+ {:date => Date.today }, {:date => Date.yesterday }
100
+ ]
101
+ @shuttle.save!
102
+ }.to change(Launch, :count).by(2)
103
+ @shuttle.launches.order(:date).map(&:date).should eq [ Date.yesterday, Date.today ]
104
+ # do the same for a new object
105
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
106
+ expect {
107
+ expect {
108
+ shuttle.launches_attributes = [
109
+ {:date => Date.today }, {:date => Date.yesterday }
110
+ ]
111
+ shuttle.save!
112
+ }.to change(Launch, :count).by(2)
113
+ }.to change(SpaceShuttle, :count).by(1)
114
+ shuttle.launches.order(:date).map(&:date).should eq [ Date.yesterday, Date.today ]
115
+ end
62
116
 
63
117
  it "can use has_many :through associations defined in ascendant classes" do
64
118
  experiment1 = Experiment.new(:name => 'Zero-gravity')
@@ -77,6 +131,41 @@ describe SpaceShuttle do
77
131
  @shuttle.experiment_ids = [ experiment1.id, experiment3.id ]
78
132
  @shuttle.experiment_ids.sort.should eq [ experiment1.id, experiment3.id ]
79
133
  Experiment.last.space_ships.first.specialize.id.should eq @shuttle.id
134
+ # test build functionality for adding to existing collection
135
+ expect {
136
+ @shuttle.experiments.build(:name => 'Superconductivity')
137
+ @shuttle.save!
138
+ }.to change(Experiment, :count).by(1)
139
+ @shuttle.experiments.order(:id).last.name.should eq 'Superconductivity'
140
+ # test build functionality for new collection
141
+ @shuttle.experiments.clear
142
+ @shuttle.experiment_ids.should eq []
143
+ expect {
144
+ @shuttle.name = 'Shuttle'
145
+ @shuttle.experiments.build(:name => 'Failed experiment')
146
+ @shuttle.save!
147
+ }.to change(Experiment, :count).by(1)
148
+ @shuttle.experiments.first.name.should eq 'Failed experiment'
149
+ @shuttle.name.should eq 'Shuttle'
150
+ # test build functionality for new collection and new object
151
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
152
+ expect {
153
+ expect {
154
+ shuttle.name = 'Shuttle'
155
+ shuttle.experiments.build(:name => 'Exp1')
156
+ shuttle.experiments.build(:name => 'Exp2')
157
+ shuttle.save!
158
+ }.to change(Experiment, :count).by(2)
159
+ }.to change(SpaceShuttle, :count).by(1)
160
+ shuttle.reload
161
+ shuttle.name.should eq 'Shuttle'
162
+ shuttle.experiments.order(:name).map(&:name).should eq ['Exp1', 'Exp2']
163
+ # test adding onto existing collection (new object)
164
+ expect {
165
+ shuttle.experiments.build(:name => 'Exp3')
166
+ shuttle.save!
167
+ }.to change(Experiment, :count).by(1)
168
+ shuttle.experiments.order(:name).map(&:name).should eq ['Exp1', 'Exp2', 'Exp3']
80
169
  end
81
170
 
82
171
  it "supports operations on the 'remote' side of a has_many :through association" do
@@ -97,6 +186,27 @@ describe SpaceShuttle do
97
186
  }.to change(ExperimentSpaceShipPerformance, :count).by(-1)
98
187
  end
99
188
 
189
+ it "supports accepts_nested_attributes for has_many :through associations defined in ascendant classes" do
190
+ expect {
191
+ @shuttle.experiments_attributes = [
192
+ {:name => 'Exp1'}, {:name => 'Exp2'}
193
+ ]
194
+ @shuttle.save!
195
+ }.to change(Experiment, :count).by(2)
196
+ @shuttle.experiments.order(:name).map(&:name).should eq [ 'Exp1', 'Exp2' ]
197
+ # do the same for a new object
198
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
199
+ expect {
200
+ expect {
201
+ shuttle.experiments_attributes = [
202
+ {:name => 'Exp1'}, {:name => 'Exp2'}
203
+ ]
204
+ shuttle.save!
205
+ }.to change(Experiment, :count).by(2)
206
+ }.to change(SpaceShuttle, :count).by(1)
207
+ shuttle.experiments.order(:name).map(&:name).should eq [ 'Exp1', 'Exp2' ]
208
+ end
209
+
100
210
  it "can use has_one associations defined in ascendant classes" do
101
211
  captain = Captain.new(:name => 'Armstrong')
102
212
  expect {
@@ -110,11 +220,24 @@ describe SpaceShuttle do
110
220
  @shuttle.create_captain(:name => 'Glenn')
111
221
  }.to change(Captain, :count).by(1)
112
222
  @shuttle.captain.space_ship_id.should eq @shuttle.convert_to(:space_ship).id
223
+ Captain.all.map(&:destroy)
224
+ # test build for existing object
113
225
  expect {
114
226
  cap = @shuttle.build_captain(:name => 'Aldrinn')
115
- cap.save!
227
+ @shuttle.save!
116
228
  }.to change(Captain, :count).by(1)
117
229
  @shuttle.captain.space_ship_id.should eq @shuttle.convert_to(:space_ship).id
230
+ # test build for new object
231
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
232
+ expect {
233
+ expect {
234
+ shuttle.name = 'Shuttle'
235
+ cap = shuttle.build_captain(:name => 'Aldrinn')
236
+ shuttle.save!
237
+ }.to change(Captain, :count).by(1)
238
+ }.to change(SpaceShuttle, :count).by(1)
239
+ shuttle.name.should eq 'Shuttle'
240
+ shuttle.captain.space_ship_id.should eq shuttle.convert_to(:space_ship).id
118
241
  end
119
242
 
120
243
  it "supports operations on the 'remote' side of a has_one association" do
@@ -131,6 +254,23 @@ describe SpaceShuttle do
131
254
  }.to change(Captain, :count).by(1)
132
255
  end
133
256
 
257
+ it "supports accepts_nested_attributes for has_one associations defined in ascendant classes" do
258
+ expect {
259
+ @shuttle.captain_attributes = {:name => 'Haddock'}
260
+ @shuttle.save!
261
+ }.to change(Captain, :count).by(1)
262
+ @shuttle.captain.name.should eq 'Haddock'
263
+ # do the same for a new object
264
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
265
+ expect {
266
+ expect {
267
+ shuttle.captain_attributes = {:name => 'Haddock'}
268
+ shuttle.save!
269
+ }.to change(Captain, :count).by(1)
270
+ }.to change(SpaceShuttle, :count).by(1)
271
+ shuttle.captain.name.should eq 'Haddock'
272
+ end
273
+
134
274
  it "can use has_and_belongs_to_many associations defined in ascendant classes" do
135
275
  astronaut1 = Astronaut.new(:name => 'Armstrong')
136
276
  astronaut2 = Astronaut.new(:name => 'Glenn')
@@ -148,6 +288,41 @@ describe SpaceShuttle do
148
288
  astronaut3.save!
149
289
  @shuttle.astronaut_ids = [ astronaut1.id, astronaut3.id ]
150
290
  @shuttle.astronaut_ids.sort.should eq [ astronaut1.id, astronaut3.id ]
291
+ # test build functionality for adding to existing collection
292
+ expect {
293
+ @shuttle.astronauts.build(:name => 'astro1')
294
+ @shuttle.save!
295
+ }.to change(Astronaut, :count).by(1)
296
+ @shuttle.astronauts.order(:id).last.name.should eq 'astro1'
297
+ # test build functionality for new collection
298
+ @shuttle.astronauts.clear
299
+ @shuttle.astronaut_ids.should eq []
300
+ expect {
301
+ @shuttle.name = 'Shuttle'
302
+ @shuttle.astronauts.build(:name => 'astro2')
303
+ @shuttle.save!
304
+ }.to change(Astronaut, :count).by(1)
305
+ @shuttle.astronauts.first.name.should eq 'astro2'
306
+ @shuttle.name.should eq 'Shuttle'
307
+ # test build functionality for new collection and new object
308
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
309
+ expect {
310
+ expect {
311
+ shuttle.name = 'Shuttle'
312
+ shuttle.astronauts.build(:name => 'Astro1')
313
+ shuttle.astronauts.build(:name => 'Astro2')
314
+ shuttle.save!
315
+ }.to change(Astronaut, :count).by(2)
316
+ }.to change(SpaceShuttle, :count).by(1)
317
+ shuttle.reload
318
+ shuttle.name.should eq 'Shuttle'
319
+ shuttle.astronauts.order(:name).map(&:name).should eq ['Astro1', 'Astro2']
320
+ # test adding onto existing collection (new object)
321
+ expect {
322
+ shuttle.astronauts.build(:name => 'Astro3')
323
+ shuttle.save!
324
+ }.to change(Astronaut, :count).by(1)
325
+ shuttle.astronauts.order(:name).map(&:name).should eq ['Astro1', 'Astro2', 'Astro3']
151
326
  end
152
327
 
153
328
  it "supports operations on the 'remote' side of a has_and_belongs_to_many association" do
@@ -167,7 +342,45 @@ describe SpaceShuttle do
167
342
  astronaut.space_ships.destroy(@shuttle)
168
343
  ActiveRecord::Base.connection().execute(query)[0]['count'].to_i.should be_zero
169
344
  end
345
+
346
+ it "supports accepts_nested_attributes for has_and_belongs_to_many associations defined in ascendant classes" do
347
+ expect {
348
+ @shuttle.astronauts_attributes = [
349
+ {:name => 'Astro1'}, {:name => 'Astro2'}
350
+ ]
351
+ @shuttle.save!
352
+ }.to change(Astronaut, :count).by(2)
353
+ @shuttle.astronauts.order(:name).map(&:name).should eq [ 'Astro1', 'Astro2' ]
354
+ # do the same for a new object
355
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
356
+ expect {
357
+ expect {
358
+ shuttle.astronauts_attributes = [
359
+ {:name => 'Astro1'}, {:name => 'Astro2'}
360
+ ]
361
+ shuttle.save!
362
+ }.to change(Astronaut, :count).by(2)
363
+ }.to change(SpaceShuttle, :count).by(1)
364
+ shuttle.astronauts.order(:name).map(&:name).should eq [ 'Astro1', 'Astro2' ]
365
+ end
366
+
367
+ it "doesn't choke on belongs_to associations" do
368
+ @shuttle.category # should not rais exception
369
+ end
170
370
 
371
+ it "doesn't save in case of validation errors in associations defined in ascendant classes" do
372
+ expect {
373
+ @shuttle.launches.build
374
+ @shuttle.save!
375
+ }.to raise_exception(ActiveRecord::RecordInvalid)
376
+ # same with new object
377
+ shuttle = SpaceShuttle.new(:name => 'Endeavour', :reliability => 100)
378
+ expect {
379
+ shuttle.launches.build
380
+ shuttle.save!
381
+ }.to raise_exception(ActiveRecord::RecordInvalid)
382
+ end
383
+
171
384
  end
172
385
 
173
386
  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.1
4
+ version: 0.1.2
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-23 00:00:00.000000000 Z
12
+ date: 2013-10-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -81,6 +81,7 @@ files:
81
81
  - lib/db_view_cti/model/collection_delegator.rb
82
82
  - lib/db_view_cti/model/cti.rb
83
83
  - lib/db_view_cti/model/extensions.rb
84
+ - lib/db_view_cti/model/model_delegator.rb
84
85
  - lib/db_view_cti/names.rb
85
86
  - lib/db_view_cti/railtie.rb
86
87
  - lib/db_view_cti/schema_dumper.rb
@@ -104,6 +105,7 @@ files:
104
105
  - spec/dummy-rails-3/app/models/astronaut.rb
105
106
  - spec/dummy-rails-3/app/models/captain.rb
106
107
  - spec/dummy-rails-3/app/models/car.rb
108
+ - spec/dummy-rails-3/app/models/category.rb
107
109
  - spec/dummy-rails-3/app/models/experiment.rb
108
110
  - spec/dummy-rails-3/app/models/experiment_space_ship_performance.rb
109
111
  - spec/dummy-rails-3/app/models/launch.rb
@@ -135,6 +137,7 @@ files:
135
137
  - spec/dummy-rails-3/db/migrate/20130713133605_create_motor_vehicles.rb
136
138
  - spec/dummy-rails-3/db/migrate/20130713133705_create_cars.rb
137
139
  - spec/dummy-rails-3/db/migrate/20130713153631_create_motor_cycles.rb
140
+ - spec/dummy-rails-3/db/migrate/20130816022112_create_categories.rb
138
141
  - spec/dummy-rails-3/db/migrate/20130816022212_create_space_ships.rb
139
142
  - spec/dummy-rails-3/db/migrate/20130817014120_create_space_shuttles.rb
140
143
  - spec/dummy-rails-3/db/migrate/20130817024220_create_rocket_engines.rb
@@ -211,7 +214,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
211
214
  version: '0'
212
215
  segments:
213
216
  - 0
214
- hash: 3228810689469855643
217
+ hash: -748447197345162789
215
218
  required_rubygems_version: !ruby/object:Gem::Requirement
216
219
  none: false
217
220
  requirements:
@@ -220,7 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
223
  version: '0'
221
224
  segments:
222
225
  - 0
223
- hash: 3228810689469855643
226
+ hash: -748447197345162789
224
227
  requirements: []
225
228
  rubyforge_project:
226
229
  rubygems_version: 1.8.25