dbview_cti 0.1.1 → 0.1.2

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,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