dbview_cti 0.1.3 → 0.1.4

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/.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