I'd like to catch and remove method calls in my Rails models in certain cases.
I'd like something like the remove_method, but that removes method calls.
For example, if I have a before_save callback in a model, I may want to catch it using a module that's extended into the class, and remove or prevent the before_save from firing.
Is this possible?
Thanks.
Edit:
The pervious answer I posted does not work - not sure if it ever did, but I vaguely recall using in the past. To skip callbacks, in the context of a subclass, for example, you should use skip_callback. Example of the usage:
skip_callback :save, :before, :callback_method_name
Or if you want to skip callbacks under a for a particular condition:
skip_callback :save, :before, :callback_method_name, if: :some_condition_true?
There is also a reset_callbacks method that takes the callback type as a parameter. For example, reset_callbacks :save. The callbacks can be accessed via the class methods, _save_callbacks, _create_callbacks, etc. I would use caution when using reset_callbacks since it appears that Rails itself is defining callbacks for associations, namely, :before_save_collection_association.
If you're talking about just regular Ruby objects, you can override methods in subclasses. Just define a method with the same name as the superclass. You can also override base class methods in a subclass by including a module which defines the same instance methods as the base class.
Have you tried clearing before_save like this:
before_save ->{}
Related
I understand what these statements do, but not how to refer to them. They exist within a class, outside of that class's methods and perform a variety of functions.
Collectively, what are they called?
These methods are really just class methods. Try this:
class Test
def self.before_create
puts "before_create"
end
before_create
end
The specific use case you mentioned - Rails DSL methods such as before_create, that are only available inside a class body — are often called class macros. Rubys metaprogramming abilities give you multiple ways to build them. A simple one is to make them private:
module Foo
private
def before_create
puts "before_create"
end
end
class Bar
extend Foo
before_create
end
before_create is now accessible inside the class body, but not from outside:
Bar.before_create
NoMethodError: private method `before_create' called for Bar:Class
In pure Ruby terms, they are all just method calls.
However, they do have a common theme. In the way they are constructed and used, you could consider them part of a Domain-Specific Language (DSL) - the ones you list are part of Active Record's DSL for creating data models.
Ruby lends itself well to creating DSL-like mini languages, using mix-ins or a base class in order to provide a set of class methods, which in turn will store data or create methods on the class and instances of it using meta-programming techniques.
I want to create matcher that test whether a model is watched by observer.
I decided to dynamically add method after_create (if necessary), save instance of model and check is it true that observer instance received an after_create call. Simplified version (full version) :
RSpec::Matchers.define :be_observed_by do |observer_name|
match do |obj|
...
observer.class_eval do
define_method(:after_create) {}
end
observer.instance.should_receive(:after_create)
obj.save(validate: false)
...
begin
RSpec::Mocks::verify # run mock verifications
true
rescue RSpec::Mocks::MockExpectationError => e
# here one can use #{e} to construct an error message
false
end
end
end
It wasn't work. No instance of observer is received after_create call.
But If I modify actual code of Observer in app/models/user_observer.rb like this
class UserObserver
...
def after_create end
...
end
It works as expected.
What should I do to add after_create method dynamically to force trigger observer after create?
In short, this behavior is due to the fact that Rails hooks up UserObserver callbacks to User events at the initialization time. If the after_create callback is not defined for UserObserver at that time, it will not be called, even if later added.
If you are interested in more details on how that observer initialization and hook-up to the wobserved class works, at the end I posted a brief walk-through through the Observer implementation. But before we get to that, here is a way to make your tests work. Now, I'm not sure if you want to use that, and not sure why you decided to test the observer behavior in the first place in your application, but for the sake of completeness...
After you do define_method(:after_create) for observer in your matcher insert the explicit call to define_callbacks (a protected method; see walkthrough through the Observer implementatiin below on what it does) on observer instance. Here is the code:
observer.class_eval do
define_method(:after_create) { |user| }
end
observer.instance.instance_eval do # this is the added code
define_callbacks(obj.class) # - || -
end # - || -
A brief walk-through through the Observer implementation.
Note: I'm using the "rails-observers" gem sources (in Rails 4 observers were moved to an optional gem, which by default is not installed). In your case, if you are on Rails 3.x, the details of implementation may be different, but I believe the idea will be the same.
First, this is where the observers' instantiation is launched: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/railtie.rb#L24. Basically, call ActiveRecord::Base.instantiate_observers in ActiveSupport.on_load(:active_record), i.e. when the ActiveRecord library is loaded.
In the same file you can see how it takes the config.active_record.observers parameter normally provided in the config/application.rb and passes it to the observers= defined here: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L38
But back to ActiveRecord::Base.instantiate_observers. It just cycles through all defined observers and calls instantiate_observer for each of them. Here is where the instantiate_observer is implemented: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L180. Basically, it makes a call to Observer.instance (as a Singleton, an observer has a single instance), which will initialize that instance if that was not done yet.
This is how Observer initialization looks like: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L340. I.e. a call to add_observer!.
You can see add_observer!, together with and define_callbacks that it calls, here: https://github.com/rails/rails-observers/blob/master/lib/rails/observers/activerecord/observer.rb#L95.
This define_callbacks method goes through all the callbacks defined in your observer class (UserObserver) at that time and creates "_notify_#{observer_name}_for_#{callback}" methods for the observed class (User), and register them to be called on that event in the observed class (User, again).
In your case, it should have been _notify_user_observer_for_after_create method added as after_create callback to User. Inside, that _notify_user_observer_for_after_create would call update on the UserObserver class, which in turn would call after_create on UserObserver, and all would work from there.
But, in your case after_create doesn't exist in UserObserver during Rails initialization, so no method is created and registered for User.after_create callback. Thus, no luck after that with catching it in your tests. That little mystery is solved.
Recently I had to add a method to Redmine's core class. I was unable to use inheritance, so I've done something like this:
require_dependency 'time_entry_query'
class TimeEntryQuery < Query
def my_new_method(foo, bar)
end
end
and it works perfectly - my method is added to all new objects. However, I've seen someone declaring the new method in their own module instead and then sending :include to class, so it become a mixin. Here's an example:
module Patches
module SomeClassPatch
def my_new_method
end
end
and somewhere in app's initialization:
SomeClass.send(:include, Patches::SomeClassPatch) unless SomeClass.include? (Patches::SomeClassPatch)
What's difference between these two methods and which one should I use?
There are two differences:
When you use a mixin, there is a clear place where your "patch" methods can live. If I wonder "Hmm, where's this my_new_method" coming from, and I look at, say, TimeEntryQuery.ancestors or TimeEntryQuery.instance_method(:my_new_method).owner, that will return Patches::SomeClassPatch. So I know I have to look for a file named lib/patches/some_class_patch.rb somewhere to find where it is probably defined. (I could try source_location as well, but that is not always reliable.)
Mixing in a module into a class makes the module the superclass of the class it is being mixed into. So, if there already is a my_new_method defined in TimeEntryQuery, your first option will overwrite it, whereas in your second option, your method will become the super method of that method. IOW: with your second option, your new method won't be called unless the already existing method calls super.
I'm trying to implement a model auditor that looks for changes to the Mongo. Originally, I tried to make a base class that my models inherit from, but I found out that's not possible.
I'm adding a module to a model that relies on Mongoid. The module contains after_create, after_update, and after_destroy callbacks. And that's the problem... In order to get the callbacks to work as if they are class-level methods, I have to do something this.
module Auditor
def self.after_create
#after create code
end
end
However, I this will override any after create calls inside my model.
Is there a way I can modify my Auditor module's after_create method to accept what the model wants to run callbacks on?
I'm just starting to grok Ruby. My background is in .NET and PHP. In Rails, and I'm sure in other frameworks as well, I see stuff like this on classes:
class Person < ActiveRecord::Base
validates :terms_of_service, :acceptance => true
end
What exactly is "validates"? Is it a function? If it's a function, how does the validation actually work since you don't tell the validates function which model you are validating?
Where can I read more about how this actually works behind the scenes?
It's... a little complicated - but the short answer is that validates is a class method of Person, inherited from ActiveRecord::Base. That line could equally have been written validates(:terms_of_service, :acceptance => true).
Ruby, like a lot of interpreted languages, effectively "executes" class definitions, so when it encounters the validates line, it sees it as a method call where the current self object is the instance of the Class class representing the class Person, inheriting from ActiveRecord::Base. It calls the method, which has the effect of hooking a validator into the Person class.
You can read about the method here - but do note that that adds more confusion, since it lists the method as an instance method of ActiveModel::Validations::ClassMethods. Huh? Well, Ruby has two ways of taking functionality from another Module and putting it into your class - you can either include the module (in which case its instance methods become instance methods of your class), or extend the module (in which case its instance methods become class methods of your class).
So, to summarise: validates is declared as an instance method of ActiveModel::Validations::ClassMethods, which is extended into ActiveRecord::Base. Therefore, validates is a class method of ActiveRecord::Base and, by inheritance, Person. The line in your code snippet is just a method call.
However, having said all that, most Rubyists and Railsists will largely ignore those facts; validates is what's called a "decorator", and most people will simply read it as a statement about Person.
It's a special callback method that gets called to validate data before it's saved to the database.
A callback, more generally, is a method that "hooks into" an object or code module/library in such a way that the method gets called automatically when certain events occur. In this case, the event that calls the set of validation methods is the act of trying to save something to the database. This is useful because, before you write new or updated information to your database, you want to make sure it's valid, in order to prevent bad data from leaking into your application.
The following methods trigger validations, and will save the object to the database only if all validations pass (in the literal sense, this means all the validation methods must return a value that is not false, or nil, etc., otherwise validation is considered to have failed):
create
create!
save
save!
update
update_attributes
update_attributes!
"Methods" in Ruby are very similar to the concept of functions in other languages. In many cases you can think of them interchangeably - they declare some functionality, they take parameters as input, and they return a result as output.
More detail on validations: http://guides.rubyonrails.org/active_record_validations_callbacks.html
Ruby (and Rails) use "callbacks" in many different situations - pretty much anytime you want some method to be called as a result of some event occuring.
More reading on callbacks: http://www.khelll.com/blog/ruby/ruby-callbacks/