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
Related
I have the following model:
class TwitterEngagement < ApplicationRecord
end
And I would like to override create (and create!), update (and
update!) methods of it so no one can manually entry fake data. I would like the help of someone more experienced with active record and rails so I don't mess anything up. Right now what I have is:
class TwitterEngagement < ApplicationRecord
belongs_to :page
def create
super(metrics)
end
def update
super(metrics)
end
private
def metrics
client.get_engagements(page.url)
def client
TwitterClient.new
end
end
Thank you.
TL;DR:
class FacebookEngagement < ApplicationRecord
def create_or_update(*args, &block)
super(metrics)
end
Probably depends on your Rails version, but I traced the ActiveRecord::Persistence sometime before in Rails 5, and found out that both create and update eventually calls create_or_update.
Suggestion:
If ever possible, I'll just do a validation, because it kinda makes more sense because you are validating the inputs, and then probably set an optional readonly?, to prevent saving of records. This will also prevent "silent failing" code / behaviour as doing TL;DR above would not throw an exception / populate the validation errors, if say an unsuspecting developer does: facebook_engagement.update(someattr: 'somevalue') as the arguments are gonna basically be ignored because it's instead calling super(metrics), and would then break the principle of least surprise.
So, I'll probably do something like below:
class FacebookEngagement < ApplicationRecord
belongs_to :page
validate :attributes_should_not_be_set_manually
before_save :set_attributes_from_facebook_engagement
# optional
def readonly?
# allows `create`, prevents `update`
persisted?
end
private
def attributes_should_not_be_set_manually
changes.keys.except('page_id').each do |attribute|
errors.add(attribute, 'should not be set manually!')
end
end
def set_attributes_from_facebook_engagement
assign_attributes(metrics)
end
def metrics
# simple memoization to prevent wasteful duplicate requests (or remove if not needed)
#metrics ||= graph.get_object("#{page.url}?fields=engagement")
end
def graph
Koala::Facebook::API.new
end
end
I have a model base_table, and I have a extended_table which has extra properties to further extend my base_table. (I would have different extended_tables, to add different properties to my base_table, but that's non-related to the question I'm asking here).
The model definition for my base_table is like:
class BaseTable < ActiveRecord::Base
module BaseTableInclude
def self.included(base)
base.belongs_to :base_table, autosave:true, dependent: :destroy
# do something more when this module is included
end
end
end
And the model definition for my extended_table is like:
class TennisQuestionaire < ActiveRecord::Base
include BaseTable::BaseTableInclude
end
Now I what I want is the code below:
params = {base_table: {name:"Songyy",age:19},tennis_ball_num:3}
t = TennisQuestionaire.new(params)
When I created my t, I want the base_table to be instantiated as well.
One fix I can come up with, is to parse the params to create the base_table object, before TennisQuestionaire.new was called upon the params. It's something like having a "before_new" filter here. But I cannot find such kind of filter when I was reading the documentation.
Additionally, I think another way is to override the 'new' method. But this is not so clean.
NOTE: There's one method called accepts_nested_attributes_for, seems to do what I want, but it doesn't work upon a belongs_to relation.
Any suggestions would be appreciated. Thanks :)
After some trails&error, the solution is something like this:
class BaseTable < ActiveRecord::Base
module BaseTableInclude
def initialize(*args,&block)
handle_res = handle_param_args(args) { |params| params[:base_table] = BaseTable.new(params[:base_table]) }
super(*args,&block)
end
private
def handle_param_args(args)
return unless block_given?
if args.length > 0
params = args[0]
if (params.is_a? Hash) and params[:base_table].is_a? Hash
yield params
end
end
end
end
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 a (cut-down) Model which looks like this:
class MyModel < ActiveRecord::Base
def self.update_status
event_time = time_ago_in_words 30.minutes.from_now
Twitter.update("Event is starting in #{event_time}")
end
end
As expected, I am getting a NoMethodError exception due to trying to use a method 'time_ago_in_words' from DateHelper. How should I accomplish this, and maybe more importantly, am I going about this the correct way?
extend ActionView::Helpers::DateHelper in your model
Change 30.mins.from_now to 30.minutes.from_now
I just tried it myself and have no problem doing the following:
class MyModel < ActiveRecord::Base
extend ActionView::Helpers::DateHelper
def self.update_status
event_time = time_ago_in_words(30.minutes.from_now)
Twitter.update("Event is starting in #{event_time}")
end
end
You have to use extend instead of include. See this article for an explanation.
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