polymorphism on models and modules - ruby-on-rails

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

Related

undefined method for model class that I want no modify by alias_method

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

Add functions to the callback's condition list in the module

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

Redmine: How to override the model method visible_condition

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)

Modularize an ActiveRecord class

If I have classes like this,
class A < ActiveRecord::Base
include ExampleModule
end
class B < ActiveRecord::Base
include ExampleModule
end
module ExampleModule
module ClassMethods
...
end
def included(base)
...
end
end
how do I get the a reference to class A or B inside of ExampleModule upon referencing including this module into either one of those classes? I'm asking this question because I wanted to do something like adding has_one :association or after_create :do_something to class A or B via including ExampleModule such as below.
class A < ActiveRecord::Base
include ExampleModule
end
class B < ActiveRecord::Base
include ExampleModule
end
module ExampleModule
has_one :association
after_create :do_something
module ClassMethods
...
end
def included(base)
...
end
end
Is there a better way to do this as well? Thanks!
If you extend ActiveSupport::Concern, you should be able to do it when the module is included:
module ExampleModule
extend ActiveSupport::Concern
def do_something
# ...
end
included do
has_one :association
after_create :do_something
end
end
If what you're wanting to do is call has_one or after_create depending on which class is including the module you can do this
module Extender
def self.included(base)
if base.name == A.name
# do stuff for A
has_one :association
elsif base.name == B.name
# do stuff for B
after_create :do_something
end
end
end

Rails 3 Including Nested Module Inside Controller

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

Resources