class Callbacks
def self.before_actions
#before_actions ||= []
end
def self.before_action(callback)
before_actions << callback
end
def self.inherited(child_class)
before_actions.each { |f| child_class.before_actions << f }
after_actions.each { |f| child_class.after_actions << f }
end
def execute(action)
self.class.before_actions.each { |callback| send(callback) }
send(action)
self.class.after_actions.each { |callback| send(callback) }
end
end
class Test < Callbacks
before_action :hello
def heraks
puts "heraks"
end
private
def hello
puts "Hello"
end
end
Test.new.execute(:heraks)
This works, but if I write #before_actions = [], without ||, then it doesn't work.
If I change how callbacks are stored to this:
##callbacks = []
def self.before_actions(action)
##callbacks << action
end
it works.
I used byebug and checked that Test.before_actions == [] and before_action :hello don't add to the array.
What is the difference between them? Is this a bug?
When you use a class instance variable (#before_actions) and self.before_actions uses ||= your code works; fine.
When self.before_actions uses = instead of ||= your code fails because every time you call before_actions it resets #before_actions to []. No callback will stay defined long enough to do anything.
Your version of your code that uses a class variable (##callbacks) sort of works because you're initializing ##callbacks only once outside the accessor. However, you'll have problems as soon as you have two subclasses of Callbacks: Callbacks and its subclasses will all share the same ##callbacks, so you won't be able to have different subclasses with different callbacks.
Related
Lets say I have a class with hundreds of instance methods in it. Now I have the requirement to run each method only if a certain thing is detected. Also I want to run my detection algorithm once for whole class instance no matter how many methods got called. If not detected first time no methods get called. I cannot afford if else around that many methods so I have to get a workaround. I have the following said class:
class CrawlerModule
extend Callbacks
before_run [:method_names, :of, :my, :class], :check_if_detected
#detected = nil
def check_if_detected
if #detected.nil?
detect
end
#detected
end
#hundreds of methods
private
def detect
detected_now = #my_detection_algorithm
#detected = detected_now
end
end
What I have done so far is to include following Callbacks module to call my check_if_detected method before every method but it doesn't work because method_added called at the very start of program and my detect function need some things to get initialized before detection. So the result array is always nil. Here's that complete module:
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|
result = run_callbacks_for(method_name)
if result[0] || (self.class.callbacks.values.flatten.include? method_name)
send(original_method_name, *args)
end
end
end
def should_override?(method_name)
overridden_methods.include?(method_name) || method_name =~ /original_/
end
def before_run(method_names, callback)
method_names.each do |method_name|
callbacks[method_name] << callback unless method_name.eql? callback
end
end
module InstanceMethods
def run_callbacks_for(method_name)
result = []
self.class.callbacks[method_name].to_a.each do |callback|
result << send(callback)
end
result
end
end
end
This solution came to me while trying to get to sleep, so pardon the brevity and untested code.
Forget all of the callback stuff. Instead...
You could rename every method to include a prefix like prefix_method_name (or suffix if you prefer).
Then implement a method_missing method which implements your check, and then calls the appropriate method afterward.
Something like this:
def method_missing(method_name, *args, &block)
if detected_now
send("prefix_#{method_name}")
end
end
And then to run the detection once for the whole class instance do it in the constructor:
def initialize
detected_now
super
end
Cache the detected_now results if you wish in an instance variable as normal and work with it that way if that is something you want to do.
I need a class variable which does not get inherited, so I decided to use a class instance variable. Currently I have this code:
class A
def self.symbols
history_symbols
end
private
def self.history_tables
##history_tables ||= ActiveRecord::Base.connection.tables.select do |x|
x.starts_with?(SOME_PREFIX)
end
end
def self.history_symbols
Rails.cache.fetch('history_symbols', expires_in: 10.minutes) do
history_tables.map { |x| x.sub(SOME_PREFIX, '') }
end
end
end
Can I safely transform ##history_tables to #history_tables without braking anything? Currently all my tests pass, but I am still not sure if this could be done just like that.
Since you wish to use the instance variable, you shall use instance of the class, instead of the singleton methods:
class A
def symbols
history_symbols
end
private
def history_tables
#history_tables ||= ActiveRecord::Base.connection.tables.select do |x|
x.starts_with?(SOME_PREFIX)
end
end
def history_symbols
Rails.cache.fetch('history_symbols', expires_in: 10.minutes) do
history_tables.map { |x| x.sub(SOME_PREFIX, '') }
end
end
end
A.new.symbols
instead of:
A.symbols
I'm trying to create a flexible dsl. I already have the DSL module, say module DSL. The DSL user can create spin-offs of this as a class. The main point of the DSL is to allow the user to create Feature object with a custom render method. There was a lot of ugly and non-DRY code backing the Feature, hence the abstraction, but the user needs a lot of control on how that feature renders, and my meta-programming is not up to the task. Let me show you how it's set up.
The DSL looks something like this:
module DSL
module ClassMethods
attr_accessor :features
def column(name, *args)
arguments = args.pop || {}
self.features = [] if self.features.nil?
self.features << Feature.new(name, arguments)
end
end
def self.included(base)
base.extend ClassMethods
end
end
end
An implementation of it would look something like this:
class DSLSpinOff
include DSL
feature :one
feature :two, render_with: :predefined_render
feature :three, render_with: :user_defined_render
feature :four, render_with: lambda {
puts "Go nuts, user!"
puts "Do as you please!"
}
def user_defined_render
#...
end
end
And finally, the feature class itself lies within the DSL, like so:
module DSL
#...
private
class Feature
attr_accessor :name, :render_with
def initialize(name, *args)
self.name = name
attributes = args.pop || {}
# somehow delegate attributes[:render_with] to the render function, handling defaults, lamdbas, function string names, etc
self.render_with = attributes.fetch(:render_with, :default_render)
end
private
def default_render
#...
end
def predefined_render
#...
end
end
end
The magic I was looking for: define_singleton_method.
module DSL
#...
private
class Feature
attr_accessor :name
def initialize(name, args)
#...
define_singleton_method :render do
if self.render_with.kind_of? Symbol
content = self.send(self.render_with)
else
content = self.render_with.call
end
content.present? ? content.to_s.html_safe : '–'
end
end
end
end
Now within the DSL I can iterate over all features and render them. It'll send itself :default_render, or some other :predefined_render, or use the block provided instead. However, this does not let users define methods on within the DSLSpinOff and pass them in, since those methods would get delegated to the DSLSpinOff class instead of the DSLSpinOff::Column class.
I suspect they would have to do something like:
feature :three, render_with: self.method(:user_defined_render)
def user_defined_render
#...
end
Edit:
I found a clean way to allow use of a default method, user-defined lambdas, pre-defined methods, and user-defined methods:
module DSL
#...
class Feature
attr_accessor :name, render_with
def initialize(name, *args)
self.name = name
self.render_with = args.has_key?(:render_with) ? args[:render_with] : :default_render
define_singleton_method :render do |object|
render_method = self.render_with.is_a?(Proc) ? renderer : method(renderer)
render_method.call
end
end
private
def default_render
#...
end
def predefined_render
#...
end
end
end
This will grab lambdas or procs if the user passes those in, otherwise, it'll use the method method to return a reference to the method defined on theFeature. The call method works on all three.
To support user-defined render methods, just have the open the class in an initializer to make it findable by the method method:
#initializers/custom_dsl_renders.rb
class DSL::Feature
def user_defined_render
#...
end
end
I have a lots of call to something like this :
User.active[0..5]
Which call :
class User
def active
(an ActiveRelation)
end
end
I am trying to do something like this for performance reasons :
class User
def active[limit]
(an ActiveRelation).limit(limit.to_a.size)
end
end
Unfortunately it doesn't work, any ideas to implement this ?
== EDIT
More cleaner :
class RelationWithLimit < ActiveRecord::Relation
def [] selector
case selector
when Integer
self.offset(selector).limit(1)
when Range
self.offset(selector.to_a[0]).limit(selector.to_a.size)
end
end
end
class ActiveRecord::Base
private
def self.relation #:nodoc:
#relation ||= RelationWithLimit.new(self, arel_table)
finder_needs_type_condition? ? #relation.where(type_condition) : #relation
end
end
You could have your own special subclass of ActiveRelation
class UserReturnRelation < ActiveRecord::Relation
def [] lim
self.limit lim
end
end
class User
def active
# Without knowing exactly what relation you are using
# One way to instantiate the UserReturnRelation for just this call
UserReturnRelation.new(self, arel_table).where("state = active")
end
end
Then User.active[5] should work as expected.
EDIT: Added instantiation info. You may want to look at Base#scoped and Base#relation for more info
Can you try it as params instead of array-indices? eg:
class User
def active(the_limit)
(an ActiveRelation).limit(the_limit)
end
end
User.active(5)
(note: not tested on any actual ActiveRelations...)
You can do it like this:
class User
def active
Limiter.new((an ActiveRelation))
end
class Limiter
def initialize(relation)
#relation = relation
end
def method_missing(method, *arguments, &block)
#relation.send(method, *arguments, &block)
end
def respond_to?(method, include_private = false)
#relation.respond_to?(method, include_private) || super
end
def [](value)
offset = value.to_a.first
limit = value.to_a.last - offset
#relation.offset(offset).limit(limit)
end
end
end
Well, you are defining the method in the wrong class. User.active[0..5] calls the class method active in User and the method [] in whatever class User.active is returning, I'll assume that it is returning an array of users, and Array has already defined the method [] so no worries about that.
You may be getting confused thinking that brackets are some kind of parenthesis for passing arguments to a function while they're not. Try this:
class User
class << self
def [](values)
self.find(values)
end
end
end
So, if you wanna use find with an arrays of ids, you may just use User[1,2,3].
Is it possible to add a callback to a single ActiveRecord instance? As a further constraint this is to go on a library so I don't have control over the class (except to monkey-patch it).
This is more or less what I want to do:
def do_something_creazy
message = Message.new
message.on_save_call :do_even_more_crazy_stuff
end
def do_even_more_crazy_stuff(message)
puts "Message #{message} has been saved! Hallelujah!"
end
You could do something like that by adding a callback to the object right after creating it and like you said, monkey-patching the default AR before_save method:
def do_something_ballsy
msg = Message.new
def msg.before_save(msg)
puts "Message #{msg} is saved."
# Calls before_save defined in the model
super
end
end
For something like this you can always define your own crazy handlers:
class Something < ActiveRecord::Base
before_save :run_before_save_callbacks
def before_save(&block)
#before_save_callbacks ||= [ ]
#before_save_callbacks << block
end
protected
def run_before_save_callbacks
return unless #before_save_callbacks
#before_save_callbacks.each do |callback|
callback.call
end
end
end
This could be made more generic, or an ActiveRecord::Base extension, whatever suits your problem scope. Using it should be easy:
something = Something.new
something.before_save do
Rails.logger.warn("I'm saving!")
end
I wanted to use this approach in my own project to be able to inject additional actions into the 'save' action of a model from my controller layer. I took Tadman's answer a stage further and created a module that can be injected into active model classes:
module InstanceCallbacks
extend ActiveSupport::Concern
CALLBACKS = [:before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_commit]
included do
CALLBACKS.each do |callback|
class_eval <<-RUBY, __FILE__, __LINE__
#{callback} :run_#{callback}_instance_callbacks
def run_#{callback}_instance_callbacks
return unless #instance_#{callback}_callbacks
#instance_#{callback}_callbacks.each do |callback|
callback.call
end
end
def #{callback}(&callback)
#instance_#{callback}_callbacks ||= []
#instance_#{callback}_callbacks << callback
end
RUBY
end
end
end
This allows you to inject a full set of instance callbacks into any model just by including the module. In this case:
class Message
include InstanceCallbacks
end
And then you can do things like:
m = Message.new
m.after_save do
puts "In after_save callback"
end
m.save!
To add to bobthabuilda's answer - instead of defining the method on the objects metaclass, extend the object with a module:
def do_something_ballsy
callback = Module.new do
def before_save(msg)
puts "Message #{msg} is saved."
# Calls before_save defined in the model
super
end
end
msg = Message.new
msg.extend(callback)
end
This way, you can define multiple callbacks, and they will be executed in the opposite order you added them.
The following will allow you to use an ordinary before_save construction, i.e. calling it on the class, only in this case, you call it on the instance's metaclass so that no other instances of Message shall be affected. (Tested in Ruby 1.9, Rails 3.13)
msg = Message.new
class << msg
before_save -> { puts "Message #{self} is saved" } # Here, `self` is the msg instance
end
Message.before_save # Calling this with no args will ensure that it gets added to the callbacks chain (but only for your instance)
Test it thus:
msg.save # will run the before_save callback above
Message.new.save # will NOT run the before_save callback above