How to implement a callback in Ruby?
The ruby equivalent, which isn't idiomatic, would be:
def my_callback(a, b, c, status_code)
puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
end
def do_stuff(a, b, c, callback)
sum = a + b + c
callback.call(a, b, c, sum)
end
def main
a = 1
b = 2
c = 3
do_stuff(a, b, c, method(:my_callback))
end
The idiomatic approach would be to pass a block instead of a reference to a method. One advantage a block has over a freestanding method is context - a block is a closure, so it can refer to variables from the scope in which it was declared. This cuts down on the number of parameters do_stuff needs to pass to the callback. For instance:
def do_stuff(a, b, c, &block)
sum = a + b + c
yield sum
end
def main
a = 1
b = 2
c = 3
do_stuff(a, b, c) { |status_code|
puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
}
end
Using method callbacks in plain Ruby class
A naive implementation would be;
module Callbacks
def self.extended(base)
base.send(:include, InstanceMethods)
end
def overridden_methods
@overridden_methods ||= []
end
def callbacks
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end
def method_added(method_name)
return if should_override?(method_name)
overridden_methods << method_name
original_method_name = "original_#{method_name}"
alias_method(original_method_name, method_name)
define_method(method_name) do |*args|
run_callbacks_for(method_name)
send(original_method_name, *args)
end
end
def should_override?(method_name)
overridden_methods.include?(method_name) || method_name =~ /original_/
end
def before_run(method_name, callback)
callbacks[method_name] << callback
end
module InstanceMethods
def run_callbacks_for(method_name)
self.class.callbacks[method_name].to_a.each do |callback|
send(callback)
end
end
end
end
class Foo
extend Callbacks
before_run :bar, :zoo
def bar
puts 'bar'
end
def zoo
puts 'This runs everytime you call `bar`'
end
end
Foo.new.bar #=> This runs everytime you call `bar`
#=> bar
The tricky point in this implementation is, method_added
. Whenever a method gets bind, method_added
method gets called by ruby with the name of the method. Inside of this method, what I am doing is just name mangling and overriding the original method with the new one which first runs the callbacks then calls the original method.
Note that, this implementation neither supports block callbacks nor callbacks for super class methods. Both of them could be implemented easily though.
How to properly make a callback to create a model, after initialize of another model?
As prasannaboga already wrote in his comment. Callbacks like these are running in the context of an instance. That means you do not need to call a specific user
first because the current object is already that user.
Additionally, in the context of a model, the redirect_to
call doesn't make any sense. Models don't know anything about requests and responses. redirect_to
is a controller method and you need to add it to your controller.
The following callback method should work:
def create_account
Account.create(owner_id: id, name: "#{name}'s Account")
end
I wonder why you decided to not add an has_one :account
or has_many :accounts
association to your User
model? By doing so you could simplify the method even more because then Rails would handle setting the id
automatically, like this
build_account(name: "#{name}'s Account") # when `has_one` or
accounts.build(name: "#{name}'s Account") # when `has_many`
Define custom callbacks on ruby method
I would do something like this:
require 'active_support'
class Base
include ActiveSupport::Callbacks
define_callbacks :notifier
set_callback :notifier, :after do |object|
notify()
end
def notify
puts "notified successfully"
end
def call
run_callbacks :notifier do
do_call
end
end
def do_call
raise 'this should be implemented in children classes'
end
end
class NewPost < Base
def do_call
puts "Creating new post on WordPress"
end
end
person = NewPost.new
person.call
Another solution without ActiveSupport:
module Notifier
def call
super
puts "notified successfully"
end
end
class NewPost
prepend Notifier
def call
puts "Creating new post on WordPress"
end
end
NewPost.new.call
You should check your ruby version prepend
is a "new" method (2.0)
How to create a common after_create callback for all models?
You can have a GenericModule
and include this in any model you wish
module GenericModule
extend ActiveSupport::Concern
included do
after_create :log_creation
end
def log_creation
# perform logging
end
end
And in the model
class User < ActiveRecord::Base
include GenericModule
# ...other model code...
end
You can have this for all your models in which you need this behavior.
Can you manually trigger a callback in Ruby on Rails?
If you're happy to run both :before and :after hooks, you can try run_callbacks
.
From the docs:
run_callbacks(kind, &block)
Runs the callbacks for the given event.
Calls the before and around callbacks in the order they were set, yields the block (if given one), and then runs the after callbacks in reverse order.
If the callback chain was halted, returns false. Otherwise returns the result of the block, or true if no block is given.
run_callbacks :save do
save
end
Custom Hook/Callback/Macro Methods
Here's a solution that uses prepend
. When you call before_operations
for the first time it creates a new (empty) module and prepends it to your class. This means that when you call method foo
on your class, it will look first for that method in the module.
The before_operations
method then defines simple methods in this module that first invoke your 'before' method, and then use super
to invoke the real implementation in your class.
class ActiveClass
def self.before_operations(before_method,*methods)
prepend( @active_wrapper=Module.new ) unless @active_wrapper
methods.each do |method_name|
@active_wrapper.send(:define_method,method_name) do |*args,&block|
send before_method
super(*args,&block)
end
end
end
end
class SubClass < ActiveClass
before_operations :first_validate_something, :do_this_method, :do_that_method
def do_this_method(*args,&block)
p doing:'this', with:args, and:block
end
def do_that_method; end
private
def first_validate_something
p :validating
end
end
SubClass.new.do_this_method(3,4){ |x| p x }
#=> :validating
#=> {:doing=>"this", :with=>[3, 4], :and=>#<Proc:0x007fdb1301fa18@/tmp.rb:31>}
If you want to make the idea by @SteveTurczyn work you must:
- receive the args params in the block of
define_method
, not as arguments to it. - call
before_operations
AFTER your methods have been defined if you want to be able to alias them.
class ActiveClass
def self.before_operations(before_method, *methods)
methods.each do |meth|
raise "No method `#{meth}` defined in #{self}" unless method_defined?(meth)
orig_method = "_original_#{meth}"
alias_method orig_method, meth
define_method(meth) do |*args,&block|
send before_method
send orig_method, *args, &block
end
end
end
end
class SubClass < ActiveClass
def do_this_method(*args,&block)
p doing:'this', with:args, and:block
end
def do_that_method; end
before_operations :first_validate_something, :do_this_method, :do_that_method
private
def first_validate_something
p :validating
end
end
SubClass.new.do_this_method(3,4){ |x| p x }
#=> :validating
#=> {:doing=>"this", :with=>[3, 4], :and=>#<Proc:0x007fdb1301fa18@/tmp.rb:31>}
Related Topics
How to Get a Particular Line from a File
How to Change Column Type in Heroku
How to Find Out Who Is Connected to Actioncable
Ruby Block to String Instead of Executing
Show Full Path Name of the Ruby File When It Get Loaded
Parse Email Addresses for "From" and "To" Fields in Ruby
What Does ::Myclass Ruby Scope Operator Do
Trying to Install Ruby-Filemagic on Snow Leopard Using Brew Rather Than Ports
Sort a Collection of Objects by Number (Highest First) Then by Letter (Alphabetical)
Check If an Array Is Subset of Another Array in Ruby
Missing Host to Link To! Please Provide the :Host Parameter, for Rails 4
Determining If a Variable Is Within Range
How to Solve Insecure World Writable Dir /Usr in Path,Mode 040777 Warning on Ruby