Skip to content
This repository

Callback collision #269

Closed
wants to merge 3 commits into from

5 participants

Michael Andrews Geoffrey Hichborn Blake Gentry José Valim Prem Sichanugrist
Michael Andrews

Add unit test to demonstrate issue with similar callback methods overriding each other in distinct contexts

Geoffrey Hichborn
phene commented

I think this also affects after_commit callbacks. Should we add a test for that, too?

Blake Gentry
bgentry commented

Can we please get this merged in prior to the 3.1 release?

José Valim
Owner

Thanks for the patch with the fix but it does not make sense to hardcode options[:on]. The :on option just makes sense for a few callbacks and does not belong conceptually in AS::Callbacks. I am still unsure on how to properly solve the issue. Maybe we should allow :on to receive an array?

Blake Gentry
bgentry commented

I would be even happier if I could do :on => [ :create, :update ]

José Valim
Owner

@bgentry that is easier to implement. You just need to rewrite this:

https://github.com/rails/rails/blob/master/activemodel/lib/active_model/validations.rb#L136

As something like:

options[:if].unshift("#{Array.wrap(options[:on]).inspect}.include?(validation_context)")

Also in a couple other places like:

https://github.com/rails/rails/blob/master/activemodel/lib/active_model/validations/callbacks.rb
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/transactions.rb

Michael Andrews

@josevalim- I agree that hard coding :on is not appropriate. How about instead of white listing options that imply difference, we black list options that determine equivalence.

José Valim
Owner

@mandrews I believe black listing would work fine. +1

Prem Sichanugrist
Collaborator

Note: the target of this commit is currently goes to 3.0.6, which is so old. I believe there's no way to change it, so maybe @josevalim can merge it into master instead.

I believe this is a bugfix, so targets are for 3-0-stable, 3-0-9, 3-1-stable and master. And please don't forget the CHANGELOG entry. ;)

Michael Andrews

I rebased to master, ran the unit tests and created a new pull request:
#1777

José Valim
Owner

Closed in favor of #1777

José Valim josevalim closed this
Michael Andrews mandrews referenced this pull request
Closed

Callback collision #1777

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
51 activerecord/test/cases/callbacks_test.rb
@@ -138,6 +138,34 @@ def history
138 138 end
139 139 end
140 140
  141 +class ContextualCallbacksDeveloper < ActiveRecord::Base
  142 + set_table_name 'developers'
  143 +
  144 + before_validation { history << :before_validation }
  145 + before_validation :before_validation_on_create_and_update, :on => :create
  146 + before_validation :before_validation_on_create_and_update, :on => :update
  147 +
  148 + validate do
  149 + history << :validate
  150 + end
  151 +
  152 + after_validation { history << :after_validation }
  153 + after_validation :after_validation_on_create_and_update, :on => :create
  154 + after_validation :after_validation_on_create_and_update, :on => :update
  155 +
  156 + def before_validation_on_create_and_update
  157 + history << "before_validation_on_#{self.validation_context}".to_sym
  158 + end
  159 +
  160 + def after_validation_on_create_and_update
  161 + history << "after_validation_on_#{self.validation_context}".to_sym
  162 + end
  163 +
  164 + def history
  165 + @history ||= []
  166 + end
  167 +end
  168 +
141 169 class CallbackCancellationDeveloper < ActiveRecord::Base
142 170 set_table_name 'developers'
143 171
@@ -286,6 +314,17 @@ def test_validate_on_create
286 314 ], david.history
287 315 end
288 316
  317 + def test_validate_on_contextual_create
  318 + david = ContextualCallbacksDeveloper.create('name' => 'David', 'salary' => 1000000)
  319 + assert_equal [
  320 + :before_validation,
  321 + :before_validation_on_create,
  322 + :validate,
  323 + :after_validation,
  324 + :after_validation_on_create
  325 + ], david.history
  326 + end
  327 +
289 328 def test_update
290 329 david = CallbackDeveloper.find(1)
291 330 david.save
@@ -345,6 +384,18 @@ def test_validate_on_update
345 384 ], david.history
346 385 end
347 386
  387 + def test_validate_on_contextual_update
  388 + david = ContextualCallbacksDeveloper.find(1)
  389 + david.save
  390 + assert_equal [
  391 + :before_validation,
  392 + :before_validation_on_update,
  393 + :validate,
  394 + :after_validation,
  395 + :after_validation_on_update
  396 + ], david.history
  397 + end
  398 +
348 399 def test_destroy
349 400 david = CallbackDeveloper.find(1)
350 401 david.destroy
12 activesupport/lib/active_support/callbacks.rb
@@ -147,8 +147,12 @@ def next_id
147 147 @@_callback_sequence += 1
148 148 end
149 149
150   - def matches?(_kind, _filter)
151   - @kind == _kind && @filter == _filter
  150 + ISOMORPHIC_OPTIONS = [:if, :unless, :per_key]
  151 +
  152 + def matches?(_kind, _filter, _options = {})
  153 + @kind == _kind && @filter == _filter &&
  154 + @options.reject { |k,v| ISOMORPHIC_OPTIONS.include?(k) } ==
  155 + _options.reject { |k,v| ISOMORPHIC_OPTIONS.include?(k) }
152 156 end
153 157
154 158 def _update_filter(filter_options, new_options)
@@ -485,7 +489,7 @@ def set_callback(name, *filter_list, &block)
485 489 end
486 490
487 491 filters.each do |filter|
488   - chain.delete_if {|c| c.matches?(type, filter) }
  492 + chain.delete_if {|c| c.matches?(type, filter, options) }
489 493 end
490 494
491 495 options[:prepend] ? chain.unshift(*(mapped.reverse)) : chain.push(*mapped)
@@ -497,7 +501,7 @@ def set_callback(name, *filter_list, &block)
497 501 def skip_callback(name, *filter_list, &block)
498 502 __update_callbacks(name, filter_list, block) do |chain, type, filters, options|
499 503 filters.each do |filter|
500   - filter = chain.find {|c| c.matches?(type, filter) }
  504 + filter = chain.find {|c| c.matches?(type, filter, options) }
501 505
502 506 if filter && options.any?
503 507 new_filter = filter.clone(chain, self)

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.