dbview_cti 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -2,7 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
- - rbx-19mode
5
+ - rbx
6
6
  env:
7
7
  - "RAILS_VERSION=3.2.0"
8
8
  - "RAILS_VERSION=4.0.0"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.1.4 (8/04/2014)
2
+
3
+ * Fixed association issues
4
+
1
5
  ## 0.1.3 (3/11/2013)
2
6
 
3
7
  * Fixed validation issue
data/Gemfile CHANGED
@@ -34,3 +34,10 @@ group :test, :development do
34
34
  gem "activerecord-postgresql-adapter", :platforms => [:ruby, :mswin, :mingw]
35
35
  gem "activerecord-jdbcpostgresql-adapter", :platforms => [:jruby]
36
36
  end
37
+
38
+ # for rubinius testing in Travis (cf. travis docs)
39
+ platforms :rbx do
40
+ gem 'racc'
41
+ gem 'rubysl', '~> 2.0'
42
+ gem 'psych'
43
+ end
@@ -14,6 +14,11 @@ module DBViewCTI
14
14
  cti_execute_sql(generator.drop_view_sql)
15
15
  end
16
16
 
17
+ def cti_view_exists?(class_name)
18
+ generator = DBViewCTI::SQLGeneration::Migration::Factory.generator(class_name)
19
+ cti_execute_sql(generator.view_exists_sql)[0]['count'].to_i > 0
20
+ end
21
+
17
22
  # use with block in up/down methods
18
23
  def cti_recreate_views_after_change_to(class_name, options = {})
19
24
  klass = class_name.constantize
@@ -10,7 +10,7 @@ module DBViewCTI
10
10
  end
11
11
 
12
12
  def <<(object, *args, &block)
13
- __getobj__.send('<<', object.convert_to(@target_class_name), *args, &block)
13
+ __getobj__.send('<<', object.try(:convert_to, @target_class_name), *args, &block)
14
14
  end
15
15
 
16
16
  def []=(*args, &block)
@@ -62,7 +62,7 @@ module DBViewCTI
62
62
  after_save :cti_save_associations
63
63
 
64
64
  # validations
65
- validate :cti_validate_associations
65
+ validate :cti_validate_associations, :cti_no_disable => true
66
66
  attr_accessor :cti_disable_validations
67
67
  end
68
68
 
@@ -164,6 +164,31 @@ module DBViewCTI
164
164
  result
165
165
  end
166
166
 
167
+ # redefine validate to always add :unless proc so we can disable the validations for an object
168
+ # by setting the cti_disable_validations accessor to true
169
+ def validate(*args, &block)
170
+ # we specifically don't want to disable balidations belonging to associations. Based on the naming
171
+ # rails uses, we return immediately in such cases (there must be a cleaner way to do this...)
172
+ return super if args.first && args.first.to_s =~ /^validate_associated_records_for_/
173
+ # rest of implementation insipred by the validate implementation in rails
174
+ options = args.extract_options!.dup
175
+ return super if options[:cti_no_disable]
176
+ if options.key?(:unless)
177
+ options[:unless] = Array(options[:unless])
178
+ options[:unless].unshift( cti_validation_unless_proc )
179
+ else
180
+ options[:unless] = cti_validation_unless_proc
181
+ end
182
+ args << options
183
+ return super(*args, &block)
184
+ end
185
+
186
+ def cti_validation_unless_proc
187
+ @cti_validation_unless_proc ||= Proc.new do |object|
188
+ object.respond_to?(:cti_disable_validations) && object.cti_disable_validations
189
+ end
190
+ end
191
+
167
192
  # redefine association class methods
168
193
  [:has_many, :has_and_belongs_to_many, :has_one].each do |name|
169
194
  self.class_eval <<-eos, __FILE__, __LINE__+1
@@ -196,11 +221,9 @@ module DBViewCTI
196
221
  [:has_many, :has_one].each do |association_type|
197
222
  @cti_associations[association_type].each do |association|
198
223
  next if @cti_redefined_remote_associations[association_type].include?( association )
199
- remote_class = association.to_s.camelize.singularize.constantize
200
- remote_associations = remote_class.reflect_on_all_associations(:belongs_to).map(&:name)
201
- remote_association = self.name.underscore.to_sym
202
- if remote_associations.include?( remote_association )
203
- cti_redefine_remote_belongs_to_association(remote_class, remote_association)
224
+ if cti_reciprocal_association_present_for?( association, :belongs_to )
225
+ remote_class = cti_association_name_to_class_name( association ).constantize
226
+ cti_redefine_remote_belongs_to_association(remote_class, cti_class_name_to_association_name.to_sym)
204
227
  @cti_redefined_remote_associations[association_type] << association
205
228
  end
206
229
  end
@@ -209,21 +232,44 @@ module DBViewCTI
209
232
  [:has_many, :has_and_belongs_to_many].each do |association_type|
210
233
  @cti_associations[association_type].each do |association|
211
234
  next if @cti_redefined_remote_associations[association_type].include?( association )
212
- remote_class = association.to_s.camelize.singularize.constantize
213
- remote_associations = remote_class.reflect_on_all_associations( association_type ).map(&:name)
214
- remote_association = self.name.underscore.pluralize.to_sym
215
- if remote_associations.include?( remote_association )
216
- cti_redefine_remote_to_many_association(remote_class, remote_association)
235
+ if cti_reciprocal_association_present_for?( association, association_type, true )
236
+ remote_class = cti_association_name_to_class_name( association ).constantize
237
+ cti_redefine_remote_to_many_association(remote_class, cti_class_name_to_association_name( true ).to_sym)
217
238
  @cti_redefined_remote_associations[association_type] << association
218
239
  end
219
240
  end
220
241
  end
221
242
  end
243
+
244
+ # Check if a reciprocal association of type 'type' is present for the given association.
245
+ # (example: check if a has_many association has a corresponding belongs_to in the remote class).
246
+ # Plural indicates wether the remote association we're looking for is plural (i.e. has_many :space_ships)
247
+ # or singular (i.e. belongs_to :space_ship).
248
+ # Normally, the method checks if the remote association refers to this class, but it is possible to
249
+ # pass in 'class_name' to check different classes
250
+ def cti_reciprocal_association_present_for?(association, type, plural = false, class_name = nil)
251
+ remote_class = cti_association_name_to_class_name( association ).constantize
252
+ remote_associations = remote_class.reflect_on_all_associations( type ).map(&:name)
253
+ remote_associations.include?( cti_class_name_to_association_name(plural, class_name).to_sym )
254
+ end
255
+
256
+ # converts e.g. SpaceShip to :space_ship (for plural == false), or :space_ships (for plural true)
257
+ def cti_class_name_to_association_name(plural = false, class_name = nil)
258
+ class_name ||= self.name
259
+ association_name = class_name.underscore
260
+ association_name = association_name.pluralize if plural
261
+ association_name
262
+ end
263
+
264
+ # converts e.g. :space_ships to SpaceShip
265
+ def cti_association_name_to_class_name(association_name)
266
+ association_name.to_s.camelize.singularize
267
+ end
222
268
 
223
269
  def cti_redefine_remote_belongs_to_association(remote_class, remote_association)
224
270
  remote_class.class_eval <<-eos, __FILE__, __LINE__+1
225
271
  def #{remote_association}=(object, *args, &block)
226
- super( object.convert_to('#{self.name}'), *args, &block )
272
+ super( object.try(:convert_to, '#{self.name}'), *args, &block )
227
273
  end
228
274
  eos
229
275
  end
@@ -231,7 +277,7 @@ module DBViewCTI
231
277
  def cti_redefine_remote_to_many_association(remote_class, remote_association)
232
278
  remote_class.class_eval <<-eos, __FILE__, __LINE__+1
233
279
  def #{remote_association}=(objects, *args, &block)
234
- super( objects.map { |o| o.convert_to('#{self.name}') }, *args, &block)
280
+ super( objects.map { |o| o.try(:convert_to, '#{self.name}') }, *args, &block)
235
281
  end
236
282
  def #{remote_association}(*args, &block)
237
283
  collection = super
@@ -42,26 +42,9 @@ module DBViewCTI
42
42
 
43
43
  private
44
44
 
45
- module DisableValidator
46
- def validate_each(record, *args)
47
- return if record.respond_to?(:cti_disable_validations) && record.cti_disable_validations
48
- super
49
- end
50
-
51
- if Rails::VERSION::MAJOR == 3
52
- def validate(record, *args)
53
- return if record.respond_to?(:cti_disable_validations) && record.cti_disable_validations
54
- super
55
- end
56
- end
57
- end
58
-
59
45
  def disable_validations(object = nil)
60
46
  object ||= @cti_converted_object
61
47
  object.cti_disable_validations = true
62
- object._validators.values.flatten.each do |validator|
63
- validator.extend( DisableValidator )
64
- end
65
48
  end
66
49
 
67
50
  module ForcePersistedState
@@ -57,6 +57,10 @@ module DBViewCTI
57
57
  "DROP VIEW #{@view_name};"
58
58
  end
59
59
 
60
+ def view_exists_sql
61
+ raise NotImplementedError, "DBViewCTI: view_exists_sql not implemented for this adapter."
62
+ end
63
+
60
64
  def create_trigger_sql
61
65
  # to be implemented by derived classes
62
66
  raise NotImplementedError, "DBViewCTI: create_trigger_sql not implemented for this adapter."
@@ -87,6 +87,10 @@ module DBViewCTI
87
87
  insert_trigger_func + insert_trigger
88
88
  end
89
89
 
90
+ def view_exists_sql
91
+ "SELECT count(*) FROM pg_views where viewname='#{@view_name}';"
92
+ end
93
+
90
94
  def drop_trigger_sql
91
95
  query = <<-eos
92
96
  DROP TRIGGER IF EXISTS #{@trigger_name} ON #{@view_name};
@@ -1,3 +1,3 @@
1
1
  module DBViewCTI
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -3,4 +3,7 @@ class Vehicle < ActiveRecord::Base
3
3
  cti_base_class
4
4
 
5
5
  validates :name, :presence => true
6
- end
6
+ validate do |vehicle|
7
+ errors.add(:base, "Block validation failed: name can't be blank") if vehicle.name.blank?
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ class CheckViewExists < ActiveRecord::Migration
2
+ def up
3
+ raise 'View should exist!' if !cti_view_exists?('Car')
4
+ end
5
+
6
+ def down
7
+ end
8
+ end
@@ -91,6 +91,12 @@ describe SpaceShuttle do
91
91
  launch.space_ship = @shuttle.convert_to(:vehicle)
92
92
  launch.save!
93
93
  }.to change(Launch, :count).by(1)
94
+ launch = Launch.new(:date => Date.today)
95
+ # also test nil assignment
96
+ expect {
97
+ launch.space_ship = nil
98
+ launch.save!
99
+ }.to change(Launch, :count).by(1)
94
100
  end
95
101
 
96
102
  it "supports accepts_nested_attributes for has_many associations defined in ascendant classes" do
@@ -184,6 +190,18 @@ describe SpaceShuttle do
184
190
  expect {
185
191
  experiment.space_ships.delete(@shuttle)
186
192
  }.to change(ExperimentSpaceShipPerformance, :count).by(-1)
193
+ # make sure nil assignments raise activerecord exceptions and not exceptions in dbview_cti code
194
+ expect {
195
+ experiment.space_ships = [ nil ]
196
+ experiment.save!
197
+ }.to raise_error(ActiveRecord::AssociationTypeMismatch)
198
+ expect {
199
+ experiment.space_ships << nil
200
+ experiment.save!
201
+ }.to raise_error(ActiveRecord::AssociationTypeMismatch)
202
+ expect {
203
+ experiment.space_ships.delete(nil)
204
+ }.to raise_error(ActiveRecord::AssociationTypeMismatch)
187
205
  end
188
206
 
189
207
  it "supports accepts_nested_attributes for has_many :through associations defined in ascendant classes" do
@@ -252,6 +270,12 @@ describe SpaceShuttle do
252
270
  captain.space_ship = @shuttle.convert_to(:vehicle)
253
271
  captain.save!
254
272
  }.to change(Captain, :count).by(1)
273
+ # also test nil assignment
274
+ captain = Captain.new(:name => 'Armstrong')
275
+ expect {
276
+ captain.space_ship = nil
277
+ captain.save!
278
+ }.to change(Captain, :count).by(1)
255
279
  end
256
280
 
257
281
  it "supports accepts_nested_attributes for has_one associations defined in ascendant classes" do
@@ -341,6 +365,18 @@ describe SpaceShuttle do
341
365
  ActiveRecord::Base.connection().execute(query)[0]['count'].to_i.should eq 1
342
366
  astronaut.space_ships.destroy(@shuttle)
343
367
  ActiveRecord::Base.connection().execute(query)[0]['count'].to_i.should be_zero
368
+ # make sure nil assignments raise activerecord exceptions and not exceptions in dbview_cti code
369
+ expect {
370
+ astronaut.space_ships = [nil]
371
+ astronaut.save!
372
+ }.to raise_error(ActiveRecord::AssociationTypeMismatch)
373
+ expect {
374
+ astronaut.space_ships << nil
375
+ astronaut.save!
376
+ }.to raise_error(ActiveRecord::AssociationTypeMismatch)
377
+ expect {
378
+ astronaut.space_ships.destroy(nil)
379
+ }.to raise_error(ActiveRecord::AssociationTypeMismatch)
344
380
  end
345
381
 
346
382
  it "supports accepts_nested_attributes for has_and_belongs_to_many associations defined in ascendant classes" do
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.3
4
+ version: 0.1.4
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-11-03 00:00:00.000000000 Z
12
+ date: 2014-04-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -148,6 +148,7 @@ files:
148
148
  - spec/dummy-rails-3/db/migrate/20131022020655_create_habtm_join_table.rb
149
149
  - spec/dummy-rails-3/db/migrate/20131022030659_create_experiments.rb
150
150
  - spec/dummy-rails-3/db/migrate/20131022030720_create_experiment_space_ship_performances.rb
151
+ - spec/dummy-rails-3/db/migrate/20140408013710_check_view_exists.rb
151
152
  - spec/dummy-rails-3/db/schema.rb
152
153
  - spec/dummy-rails-3/lib/assets/.gitkeep
153
154
  - spec/dummy-rails-3/log/.gitkeep
@@ -214,7 +215,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
214
215
  version: '0'
215
216
  segments:
216
217
  - 0
217
- hash: -3935376093015518215
218
+ hash: 1297181597632204944
218
219
  required_rubygems_version: !ruby/object:Gem::Requirement
219
220
  none: false
220
221
  requirements:
@@ -223,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
224
  version: '0'
224
225
  segments:
225
226
  - 0
226
- hash: -3935376093015518215
227
+ hash: 1297181597632204944
227
228
  requirements: []
228
229
  rubyforge_project:
229
230
  rubygems_version: 1.8.25