I want to override the model method with my plugin. I tried to create a new model Patch with method that should override like this:
require_dependency 'issue'
module IssuePatch
def self.included(base) # :nodoc:
base.send(:extend, ClassMethods)
base.send(:include, InstanceMethods)
base.class_eval do
alias_method :visible_condition, :visible_condition_with_patch
end
end
module ClassMethods
end
module InstanceMethods
def visible_condition_with_patch(user, options={})
<Do Something>
end
end
end
Rails.configuration.to_prepare do
unless Issue.included_modules.include? IssuePatch
# Issue.extend(InstanceMethods)
Issue.send(:include, IssuePatch)
end
end
Try this.
require_dependency 'issue'
module IssuePatch
def self.included(base)
base.class_eval do
def visible_condition(user, options={})
# do something
end
end
end
end
Issue.send(:include, IssuePatch) unless Issue.included_modules.include?(IssuePatch)
Related
I tried to path the ActiveRecord model validation -
require_dependency "issue"
module IssuePath
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method :strict_validate_issue, :validate_issue
alias_method :validate_issue, :unstrict_validate_issue
end
end
module ClassMethods
end
module InstanceMethods
def strict_validate_issue
if !due_date
errors.add :due_date, :due_date_empty
end
unstrict_validate_issue
end
end
end
Issue.send :include, IssuePath
But it threw an error NameError: undefined method unstrict_validate_issue' for classIssue (call 'Issue.connection' to establish a connection)'.
Instead of strict_validate_issue define unstrict_validate_issue method and call strict_validate_issue inside it.
require_dependency "issue"
module IssuePath
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method :strict_validate_issue, :validate_issue
alias_method :validate_issue, :unstrict_validate_issue
end
end
module ClassMethods
end
module InstanceMethods
def unstrict_validate_issue
if due_date.blank?
errors.add :due_date, :due_date_empty
end
strict_validate_issue
end
end
end
Issue.send :include, IssuePath
You can even make it shorter:
require_dependency "issue"
module IssuePath
extend ActiveSupport::Concern
included do
alias_method :strict_validate_issue, :validate_issue
alias_method :validate_issue, :unstrict_validate_issue
end
def unstrict_validate_issue
if due_date.blank?
errors.add :due_date, :due_date_empty
end
strict_validate_issue
end
end
Issue.send :include, IssuePath
I have:
a controller with before_filter callback defined (UPDATE : controller is in rails application and I can't modify it)
a module with some new functions, which is added to the controller (UPDATE : module is in my plugin)
I want this callback to execute before those functions too.
class MyController < ApplicationController
before_filter :prepare, :only => :show
def show
end
def edit
end
private
def prepare
end
end
module MyModule
def self.included(base)
base.send(:include, InstanceMethods)
base.before_filter :prepare, :only => [:a, :b]
end
module InstanceMethods
def a
...
end
def b
...
end
end
end
If I do it this way, the list will be redefined, and "prepare" wouldn't be called before "show".
The options I see:
def self.included(base)
base.send(:include, InstanceMethods)
base.before_filter :prepare, :only => [:a, :b, :show]
end
or
def a
prepare
...
end
def b
prepare
...
end
or
module MyModule
def self.included(base)
base.send(:include, InstanceMethods)
base.before_filter :prep, :only => [:a, :b]
end
module InstanceMethods
def prep
prepare
end
...
end
end
are SO ugly and un-DRY.
Is there any natural way to do it?
Perhaps you should
before_filter :prepare, :except => [:foo, :bar, :etc]
Which I think will pick up :a and :b.
BTW, your controller seems like it might be pretty fat. Perhaps, also, an opportunity for refactoring?
Perhaps you are looking for something like this:
module MyModule
def self.included(base)
base.send(:include, InstanceMethods)
base.extend ClassMethods
end
module ClassMethods
def prepare(*actions)
# optional - add defaults
actions = ([:a, :b] + (actions || [])).uniq
# self here is the singelton class object
self.instance_eval do
before_filter :prep, only: actions
end
end
end
module InstanceMethods
def prep
prepare
end
...
end
end
We create a prepare class method which takes a splat and then evaluates a before_filter with the splat of actions merged with defaults.
class ThingsController < ApplicationController
include MyModule
prepare :show
end
Lets say i have this module:
module Template
def self.included(base)
base.class_eval do
before_validation(:on => :create) { sanitize_text! }
end
base.extend(ClassMethods)
end
module ClassMethods
def sanitize_text!
# some stuff
end
end
end
And then I have a model defined like so:
class Invitation < ActiveRecord::Base
include Template
end
Notice that i did not override the method sanitize_text! but when I run through this Rails will give me an error:
undefined method `sanitize_text!' for #
Which i don't understand - isn't it suppose that my model Invitation would have included that method already from the module Template?
The before_validation calls the block as an instance, so there is no sanitize_text! because sanitize_text! is a class method.
Define sanitize_text! in the module, not as a class method.
module Template
...
def sanitize_text!
# some stuff
end
end
You've included it as a Class Method, not as a method on any specific instance of that model.
To use that you need to call it like:
Invitation.sanitize_text!
To call it on an instance of Invitation, make these changes:
module Template
def self.included(base)
base.class_eval do
before_validation(:on => :create) { sanitize_text! }
end
base.extend(ClassMethods)
end
def sanitize_text!
# some stuff
end
module ClassMethods
end
end
Then you'll be able to call it on a particular method:
#invitation.sanitize_text!
The way you are doing it you're extending Invitation class with method sanitize_text!, not the instances of Invitation.
If you want instance to have sanitize_text!, you should better rename ClassMethods to InstanceMethods and change self.included to have this line:
base.send :include, InstanceMethods
So the whole code would look like this:
module Template
def self.included(base)
base.class_eval do
before_validation(:on => :create) { sanitize_text! }
end
base.send :include, InstanceMethods
end
module InstanceMethods
def sanitize_text!
# method body
end
end
end
I have a Module A, and there are several Class need to Mixin it, there is a method should be wrote as Class Method of that Module, but this method need to get data from the Tables which match these Classes. It that realizable?
module Authenticate
def password=(password)
if password.present?
generate_salt
self.hashed_password = Authenticate.encrypt_password(password, salt)
end
end
class << self
def encrypt_password(password,salt)
Digest::SHA2.hexdigest(password + salt)
end
end
private
def generate_salt
self.salt = self.object_id.to_s + rand.to_s
end
end
require 'authenticate_module'
class Administrator < ActiveRecord::Base
validates :password, :confirmation => true
attr_accessor :password_confirmation
attr_reader :password
include Authenticate
end
This is that method:
def authenticate(name,password)
if user = ???.find_by_name(name)
if user.hashed_password == Authenticate.encrypt_password(password,user.salt)
user
end
end
end
Use ActiveSupport::Concern to add class methods to every class that includes your module, then calling self in that method will return the class name.
It will be something like:
module Authenticate
extend ActiveSupport::Concern
module ClassMethods
def authenticate(name, password)
self.class # returns the name of the class that includes this module
end
end
end
class User
include Authenticate
end
# Now You can call
User.authenticate(name, password)
What ActiveSupport::Concern does is that whenever a class includes the module, it extends that class with the ClassMethods which here is equivalent to doing
class User
include Authenticate
extend Authenticate::ClassMethods
end
I have a controller that I'd like to include some standard methods.
class Main::UsersController < Main::BaseController
include MyModule::ControllerMethods
end
uninitialized constanct MyModule::ClassMethods::InstanceMethods
My module looks like this, which is also wrong, and was originally meant for a model. What's the best way to do it so that I can use it with a controller as well?
module MyModule
def self.included(base)
base.has_one :example, :autosave => true
base.before_create :make_awesome
base.extend ClassMethods
end
module ClassMethods
...
include InstanceMethods
end
module InstanceMethods
...
end
module ControllerMethods
...
# I want to include these in my controller
def hello; end
def world; end
end
end
Use extend instead of include for your ClassMethods. You should also split your model and controller modules:
module MyModule
module ModelMethods
def acts_as_something
send :has_one, :example, :autosave => true
send :before_create, :make_awesome
send :include, InstanceMethods
end
module InstanceMethods
...
end
end
module ControllerMethods
...
# I want to include these in my controller
def hello; end
def world; end
end
end
ActiveRecord::Base.extend MyModule::ModelMethods
Your Model would then look like this:
class Model < ActiveRecord::Base
acts_as_something
end