I was playing around with implementing Rails model callbacks (after_save, before_save) etc. using alias method. All it does is it aliases the save method to save_with_callbacks. It works, except before_save has to be called after save is defined or alias keyword throws an error. I'm still in the process of understanding how Rails callbacks really works, but was wondering if there's a way to use before_filter anywhere in the model.
module ClassMethods
def before_save
class_eval do
# old_save points to save
# save points to save_with_callbacks
alias :old_save :save
alias :save :save_with_callbacks
end
end
end
module InstanceMethods
def save_with_callbacks
#save_with_callbacks_text = 'Saving with callbacks'
old_save
end
end
class Task
extend ClassMethods
include InstanceMethods
attr_reader :save_text, :save_with_callbacks_text
def save
#save_text = 'Saving'
end
# Needs to be called after save, save_with_callbacks are defined
before_save
end
I forgot users don't generally define 'save' method, but let ORM do it for you. Moved save method to InstanceMethods and that solves the problem.
Related
I would like to create something similar to ActiveRecord validation: before_validate do ... end. I am not sure how could I reference attributes of class instance from the block given. Any idea?
class Something
attr_accessor :x
def self.before_validate(&block)
#before_validate_block = block
end
before_validate do
self.x.downcase
end
def validate!
# how should this method look like?
# I would like that block would be able to access instance attributes
end
end
#3limin4t0r's answer covers mimicing the behavior in plain ruby very well. But if your are working in Rails you don't need to reinvent the wheel just because you're not using ActiveRecord.
You can use ActiveModel::Callbacks to define callbacks in any plain old ruby object:
class Something
extend ActiveModel::Callbacks
define_model_callbacks :validate, scope: :name
before_validate do
self.x.downcase
end
def validate!
run_callbacks :validate do
# do validations here
end
end
end
Featurewise it blows the socks off any of the answers you'll get here. It lets define callbacks before, after and around the event and handles multiple callbacks per event.
If validations are what you really are after though you can just include ActiveModel::Validations which gives you all the validations except of course validates_uniqueness_of which is defined by ActiveRecord.
ActiveModel::Model includes all the modules that make up the rails models API and is a good choice if your are declaring a virtual model.
This can be achieved by using instance_eval or instance_exec.
class Something
attr_accessor :x
# You need a way to retrieve the block when working with the
# instance of the class. So I've changed the method so it
# returns the +#before_validate_block+ when no block is given.
# You could also add a new method to do this.
def self.before_validate(&block)
if block
#before_validate_block = block
else
#before_validate_block
end
end
before_validate do
self.x.downcase
end
def validate!
block = self.class.before_validate # retrieve the block
instance_eval(&block) # execute it in instance context
end
end
How about this?
class Something
attr_accessor :x
class << self
attr_reader :before_validate_blocks
def before_validate(&block)
#before_validate_blocks ||= []
#before_validate_blocks << block
end
end
def validate!
blocks = self.class.before_validate_blocks
blocks.each {|b| instance_eval(&b)}
end
end
Something.before_validate do
puts x.downcase
end
Something.before_validate do
puts x.size
end
something = Something.new
something.x = 'FOO'
something.validate! # => "foo\n3\n"
This version allows us to define multiple validations.
How does one override a class method defined in a model concern?
This is a bit tricky since you’re not really overriding a class method right? Because it’s using the concern api of definining class methods in the class_methods block.
so say I have a concern that looks like this:
module MyConcern
extend ActiveSupport::Concern
class_methods do
def do_something
#some code
end
end
end
In model.. how would I override that method so that I could call it like we do with super when using inheritance? So in my model I’d like to go:
def self.do_something
#call module do_something
end
?
If you've included MyConcern in the model that defines self.do_something, you should just be able to use super:
module MyConcern
extend ActiveSupport::Concern
class_methods do
def do_something
puts "I'm do_something in the concern"
end
end
end
class UsesMyConcern < ActiveRecord::Base
include MyConcern
def self.do_something
super
end
end
UsesMyConcern.do_something
# => "I'm do_something in the concern"
If you haven't or don't want to include MyConcern in the model and you want to invoke do_something on the module without creating any intermediary objects, you can change your model to:
class UsesMyConcern < ActiveRecord::Base
def self.do_something
MyConcern::ClassMethods.instance_method(:do_something).bind(self).call
end
end
UsesMyConcern.do_something
# => "I'm do_something in the concern"
ActiveSupport::Concern.class_methods defines a ClassMethods module in the concern if there isn't one already, and that's where we can find the do_something method.
Why not simply call the module's method: MyConcern.do_something?
I'm not sure if there's an easy of doing super for modules (though I can see why that may be useful).
The next best solution could be doing something like calling #included_modules and manually iterating with #responds_to?:
def self.do_something
self.super_module(__method__)
end
def self.super_module(method)
self.included_modules.find { |m| m.responds_to? method }.public_send(method)
end
The old way using alias_method_chain: https://ernie.io/2011/02/03/when-to-use-alias_method_chain/
The new way (requires > ruby 2.0.0) you really should use this, as there will be a DEPRECATION WARNING when using it in rails 5.0:
http://paweljaniak.co.za/2014/09/30/understanding-ruby-module-prepend-and-include/
I'm using the callback before_update to call a function on model which set the checkbox value on my variable.
The problem is the checkbox value which is on params[:mail_checker_issue] isn't accessible on the model layer.
The question is: How to access this params using the callback before_update ? Below my code:
module IssueSetChecketIssuePatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
before_save :before_mail_checker
end
end
end
module InstanceMethods
require_dependency 'issue'
def before_mail_checker
self.set_mail_checker_issue(params[:mail_checker_issue])
end
def set_mail_checker_issue(mail)
#mail_checker = mail
end
def get_mail_checker_issue
#mail_checker
end
end
Rails.configuration.to_prepare do
Issue.send(:include, IssueSetChecketIssuePatch)
end
params are a controller concern and are wholly separate from models. Consider what should happen if you tried to save that model from a console, for example.
You need to pass the param to the model after you instantiate it from your controller, then check the value set on the model in your before_save callback.
It's also worth noting that your code is somewhat un-Rubyish (and really, looks a lot like Java!) - you could get the same effect by just defining an attr on the model.
Rails.configuration.to_prepare do
require_dependency 'issue'
class Issue
attr_accessor :mail_checker_issue
end
end
Then, once you have an issue:
# Controller code
#issue = Issue.find(params[:id])
#issue.mail_checker_issue = params[:mail_checker_issue]
You don't, models don't know about controllers or params hash.
You should include this logic at your controller instead of forcing it in a callback.
I know I can add new methods to models but I can't seem to overwrite an existing method. Here's what I have
In my User.rb
include ExtraMethods
def is_invisible?
true unless self.active?
end
In my module
module ExtraMethods
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def user_extra_methods
include ExtraMethods::InstanceMethods
end
end
module InstanceMethods
def is_invisible?
true unless self.active? || self.admin?
end
end
end
ActiveRecord::Base.send(:include, ExtraMethods)
User.send(:user_extra_methods)
What I want to happen is for the method in the plugin to override the method in the model. Any thoughts or references would be great, can't seem to find a good reference for this.
thanks!
J
The order in which you declare the class members is important.
You're performing the plugin's include before the self.active? method is declared... The model declaration will always take precedence, since it was declared later.
You'll have to resort to something like this:
http://weblog.rubyonrails.org/2006/4/26/new-in-rails-module-alias_method_chain
I'm creating a plugin and am having a hard time defining a before_save filter that calls an instance method I've just defined. Here's a quick sample:
module ValidatesAndFormatsPhones
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def validates_and_formats_phones(field_names = [:phone])
send :include, InstanceMethods
# the following variations on calls to :format_phone_fields fail
before_save send(:format_phone_fields, field_names)
before_save format_phone_fields(field_names)
before_save lambda { send(:format_phone_fields, field_names) }
# EACH OF THE ABOVE RETURNS 'undefined_method :format_phone_fields'
end
end
module InstanceMethods
def format_phone_fields(fields = [:phone], *args)
do stuff...
end
end
end
ActiveRecord::Base.send :include, ValidatesAndFormatsPhones
I guess the question is, how do I change the context to the instance, instead of the class?
I'd prefer to call the instance method because the class shouldn't really have a method called 'format_phone_fields' but the instance should.
Thanks!
Include your method at the right moment: when you're extending the base class:
module ValidatesAndFormatsPhones
def self.included(base)
base.send :extend, ClassMethods
base.send :include, InstanceMethods
end
module ClassMethods
def validates_and_formats_phones(field_names = [:phone])
before_save {|r| r.format_phone_fields(field_names)}
end
end
module InstanceMethods
def format_phone_fields(fields = [:phone], *args)
# do stuff...
end
end
end
ActiveRecord::Base.send :include, ValidatesAndFormatsPhones
I haven't run the code, but it should work. I've done similar things often enough.
as you are using callback macros, you can only pass a symbol for the method you want to run, passing arguments is not possible. the 'workaround' from the rails documentation is to use a 'method string' that gets evaluated in the right context:
before_save 'self.format_phone_fields(....)'
another possibility: store your field names as a class variable and access that one in your instance, then you can use before_save :format_phone_fields