I have around 40 models in my RoR application. I want to setup a after_save callback for all models. One way is to add it to all models. Since this callback has the same code to run, is there a way to define it globally once so that it gets invoked for all models.
I tried this with no luck:
class ActiveRecord::Base
after_save :do_something
def do_something
# ....
end
end
Same code works if I do it in individual models.
Thanks,
Imran
You should use observers for this:
class AuditObserver < ActiveRecord::Observer
observe ActiveRecord::Base.send(:subclasses)
def after_save(record)
AuditTrail.new(record, "UPDATED")
end
end
In order to activate an observer, list it in the config.active_record.observers configuration setting in your config/application.rb file.
config.active_record.observers = :audit_observer
Note
In Rails 4, the observer feature is removed from core. Use the https://github.com/rails/rails-observers gem.
I'm pretty late on this one, but in case someone else is using Rails 3 and finds this, then this response might help.
Some models might not be loaded when the observer is loaded. The documentation says that you can override observed_classes, and that way you can get the subclasses of active record dynamically.
class AuditObserver < ActiveRecord::Observer
def self.observed_classes
ActiveRecord::Base.send(:subclasses)
end
end
This seemed to work for me:
ActiveRecord::Base.after_save do
...
end
Is there a problem I'm not seeing?
Based on #harish's answer and in this answer (https://stackoverflow.com/a/10712838/2226338):
class AuditObserver < ActiveRecord::Observer
Rails.application.eager_load!
observe ActiveRecord::Base.descendants
def after_save(record)
...
end
end
This actually works pretty well for me in 2.3.8:
class AudiObserver < ActiveRecord::Observer
observe :'ActiveRecord::Base'
#
# observe methods...
#
end
Related
I would like to setup a before_create for all of my modules
what i have been trying is:
module ActiveRecord
module UserMonitor
require 'securerandom'
before_create :attach_uuid
def attach_uuid
self.uuid = SecureRandom.uuid.gsub("-","")
end
end
end
This does not seem to be working.
if i go into each module and add it in there it works, but i want to do it on a global scale.
Any thoughts or ideas on how i can achieve this in this manner? i know i could do it in triggers and such but i don't want to go that route and i would like to avoid hitting every module/class in case i need to change something.
Currently using Ruby 1.9.3 Can not currently upgrade my app until i make future code changes.
Thanks!
An other solution - I use, is to put the logic for UUID in an own module, that you include. I already have some (class-) methods I add to my AR, like set_default_if, so it was a good place for me.
module MyRecordExt
def self.included base
base.extend ClassMethods # in my case some other stuff
base.before_create :attach_uuid # now add the UUID
end
def attach_uuid
begin
self.uuid = SecureRandom.uuid
rescue
# do the "why dont we have a UUID filed?" here
end
end
# some other things not needed for add_uuid
module ClassMethods
include MySpecialBase # just an eg.
def default_for_if(...)
...
end
end
end
and then
class Articel < ActiveRecord::Base
include MyRecordExt
...
end
In general I avoid doing something for ALL models modifying AR base - I made the first bad experience with adding the UUID to all, and crashed with devise GEMs models ...
If you define attach_uuid in the ActiveRecord module, can't you just call the before_create :attach_uuid at the top of each controller? This is DRY.
Is there a UserMonitor controller that you could add it to?
class UserMonitor < ActiveRecord::Base
before_create :attach_uuid
end
I'm not phrasing the question correctly in the title, but heres what I'd like to do.
I have a method, like such:
class User < ActiveRecord::Base
def myMethod(abc, xyz)
#do stuff
end
end
I want 5 different models in my app to call this function on their after_create callback.
It seems very anti-DRY to put a function in each of those models to call this function.
Is there a way in this model (above) that holds the method - to remotely use the callbacks of the other models?
Or can anyone suggest a different way I should be approaching something like this?
That's what I would do:
Create a module:
module MyCallbacks
extend ActiveSupport::Concern
included do
after_create :my_method
end
def my_method
#do stuff
end
end
And then, you just need to include this module in the models of your choice:
class MyModel < ActiveRecord::Base
include MyCallbacks
end
I have two models which need an identical function. I'd like to learn how to make this DRY the right rails way...
For both models I have:
before_save :assign_uuid
Which in each model has:
def assign_uuid
if self.uuid.nil?
self.uuid = ActiveSupport::SecureRandom.hex(32)
end
end
Since, assign_uuid lives in both models, Where is the one place I should place this func? Also, in the models, where it say's before_save. How do I call the assign_uuid in the location it is located?
Thanks
I'm no Ruby expert, so I'm not sure if this is frowned upon or not, but if I were you, I'd chuck it in a file in lib/ and whack it straight on ActiveRecord::Base with class_eval.
ActiveRecord::Base.class_eval do
def assign_uuid
if self.uuid.nil?
self.uuid = ActiveSupport::SecureRandom.hex(32)
end
end
end
That way, it's available for all your models. Either that, or create a miscellaneous model helpers file and include the module into the models you'd like.
# lib/misc_model_helpers.rb
module MiscModelHelpers
def assign_uuid
if self.uuid.nil?
self.uuid = ActiveSupport::SecureRandom.hex(32)
end
end
end
# models/person.rb
class Person < ActiveRecord::Base
include MiscModelHelpers
before_save :assign_uuid
end
Again, I'm really not 100% on the most rubyish way of doing this. It's probably something completely different. These are just two ways that work for me!
In your lib folder, add a file uuid_lib.rb, and write
module UuidLib
def assign_uuid
if self.uuid.nil?
self.uuid = ActiveSupport::SecureRandom.hex(32)
end
end
end
and inside your model write
include UuidLib
before_save :assign_uuid
An example article explaining modules and mixins in more detail can be found here.
You should add this as a module and mix it into your models, that is the Ruby way to do what you are after.
I have a Message model class (which inherits from ActiveRecord::Base). For a particular deployment, I would like to have a separate file which modifies Message by adding a callback. So, instead of doing:
# app/models/message.rb
class Message < ActiveRecord::Base
before_save :foo
def foo
puts 'foo!'
end
end
I would like to be able to do:
# app/models/message.rb
class Message < ActiveRecord::Base
end
# config/initializers/fixes.rb
Message
class Message
before_save :foo
def foo
puts 'foo!'
end
end
Problem is, it works when I start the script/console, but when I start it using script/server it usually doesn't. That is the worst part, it isn't that it never works. Sometimes I start the server and it works, sometimes it doesn't, and that is without making any changes to the source.
I am restarting the server itself as (as far as I know) the initializers are run only once and don't get reloaded if modified.
I know the 'sometimes' works is very vague, but I have spent hours here without any luck. Perhaps someone has had a similar issue, or can come up with a different idea to add the callback.
Why not put those into a module and import it?
class Message < ActiveRecord::Base
include Message::Callbacks
end
In another file you can define whatever you like, such as message/callbacks.rb:
module Message::Callbacks
def self.included(base)
base.class_eval do
before_save :foo
end
end
def foo
# ...
end
end
The downside to this is it's more work to make the methods protected.
Why not use observers? (http://api.rubyonrails.org/classes/ActiveRecord/Observer.html)
For example, you'd do something like this:
class MessageObserver < ActiveRecord::Observer
def before_save(message)
puts 'you win at ruby!'
end
end
I would like to know if it is possible to call a method from a model after using find.
Something like after_save, but after_find.
Thank you,
Gabriel.
Nowadays ((26.04.2012) this is proper way (and working!) to do that:
class SomeClass < ActiveRecord::Base
after_find :do_something
def do_something
# code
end
end
Edit: For Rails >= 3, see the answer from #nothing-special-here
There is. Along with after_initialize, after_find is a special case, though. You have to define the method, after_find :some_method isn't enough. This should work, though:
class Post < ActiveRecord::Base
def after_find
# do something here
end
end
You can read more about it in the API.
Interestingly enough, this will call the method twice... learned that one the hard way.
class Post < ActiveRecord::Base
after_find :after_find
def after_find
# do something here
end
end
If you need the found object in your method:
class SomeClass < ActiveRecord::Base
after_find{ |o| do_something(o) }
def do_something(o)
# ...
end
end
More details here: http://guides.rubyonrails.org/active_record_callbacks.html#after-initialize-and-after-find