I'm working in some ruby code. There's a module like the below:
module ResourceTransformation
extend ActiveSupport::Concern
class_methods do
def import(resource)
end
end
end
In one of the classes that uses this module, I would like to override the import method. My current code looks like this:
class SomeEntity < ApplicationRecord
include ResourceTransformation
def import(resource)
puts "in overriden import method!!!"
# super
end
end
Unfortunately, when I call SomeEntity.import it is simply invoking and returning the results from the module import method rather than from the class import method...
How can I properly override the module method with my class method?
class_methods in your concern define class methods from given block
That's why you need override not instance but class (self) method
class SomeEntity < ApplicationRecord
include ResourceTransformation
def self.import(resource)
puts "in overriden import method!!!"
end
end
Related
I need to call a helper method within a model, from both a class and an instance method, e.g. Model.method(data) and model_instance.method. However, the class method always returns "NoMethodError: undefined method 'helper_method' for #<Class ...>"
model.rb:
class Model < ActiveRecord::Base
include ModelHelper
def method
helper_method(self.data)
end
def self.method(data)
self.helper_method(data)
end
end
model_helper.rb:
module ModelHelper
def helper_method(data)
# logic here
end
end
I even tried adding def self.helper_method(data) in the helper to no avail.
After quite a bit of seraching, I wasn't able to find anything on how to achieve this, or at least anything that worked.
The answer turned out to be pretty simple, and doesn't require any Rails magic: you just re-include the helper and define the class method within a class block:
class Model < ActiveRecord::Base
include ModelHelper
def method
helper_method(self.data)
end
# Expose Model.method()
class << self
include ModelHelper
def method(data)
helper_method(data)
end
end
end
No changes to the helper needed at all.
Now you can call method on both the class and an instance!
If there's no additional logic in method, then you can simply do:
class Model < ActiveRecord::Base
include ModelHelper
extend ModelHelper
end
And get both the instance (#model.helper_method) and the class (Model.helper_method) methods.
If, for legacy (or other) reasons, you still want to use method as an instance and class method, but method doesn't do anything different than helper_method, then you could do:
class Model < ActiveRecord::Base
include ModelHelper
extend ModelHelper
alias method helper_method
singleton_class.send(:alias_method, :method, :helper_method)
end
And now you can do #model.method and Model.method.
BTW, using modules to include methods in classes is seductive, but can get away from you quickly if you're not careful, leaving you doing a lot of #model.method(:foo).source_location, trying to figure out where something came from. Ask me how I know...
you need to define model_helper.rb as:
module ModelHelper
def self.helper_method(data)
# logic here
end
end
and call this method in model.rb as:
class Model < ActiveRecord::Base
include ModelHelper
def method
ModelHelper.helper_method(self.data)
end
def self.method(data)
ModelHelper.helper_method(data)
end
end
i have a controller inside payment folder named gateway_controller.rb
class Payment::GatewayController < PaymentController
include Payment::GatewayHelper::Handler
def set_handler
#handler = StandardGatewayInterface.get_handler("test")
end
end
And i have their helper files in helpers/payment/gateway_helper/handler.rb
which looks like
module Payment::GatewayHelper::Handler
class StandardGatewayInterface
def self.get_handler(payment_method)
puts self.descendants
end
# Get a list of subclasses.
def self.descendants
ObjectSpace.each_object(Class).select { |cls| cls < self }
end
end
end
and there is another file in /helpers/payment/test_fort.rb where there is a class that inherits from StandardGatewayInterface.
require_relative './gateway_helper/handler'
class TestFort < StandardGatewayInterface
include Payment::GatewayHelper::Handler
def build_redirect_url(user_id, product, success_url)
puts "===in==="
end
end
when i call StandardGatewayInterface.get_handler("test") it gets inside the helper method, where i call self.descendants which has to return TestFort since it inherits from StandardGatewayInterface.But what iam getting is an empty array.
How will i make this work?
class ReportMailer < ActionMailer::Base
def venues
#venues ||= Venue.find(VENUE_MAP.values)
end
end
How can I test venues, if:
ReportMailer.new => nil
How is this behavior called? is ReportMailer an abstract class?
All the methods you define in ReportMailer can be called as class methods
ReportMailer.venues
To deliver the email
ReportMailer.venues.deliver
Read
I extracted methods to module and then, included and extended it:
class ReportMailer
include ReportMailer::PrivateMethods
extend ReportMailer::PrivateMethods
...
In app/models/report_mailer/private_methods.rb:
module ReportMailer::PrivateMethods
...
I am doing this
def self.a
...
end
def a
class.a
end
But for several method I would have to replicate instance methods.
I was thinking in a module
module A
def a; end
end
And then use it in my model like this:
extend A
include A
But I am not sure where to put it according Rails folder structure, or even if to put module inside my model.
Any advice?
Opt 1 - Extend Self
If you want to have all of your instance methods as class methods as well, you can simply use extend self
class A
def foo
...
end
def bar
...
end
extend self
end
This would allow you to call foo as either A.foo or A.new.foo.
Opt 2 - Included Module
If you only want some of your instance methods to be available as class methods, then you should create a module as you proposed. You could put that module in the lib/ folder and either require it or add lib to your autoload paths.
You can also include the module directly in the class like so:
class A
def not_shared
...
end
module SharedMethods
def foo
...
end
def bar
...
end
end
extend SharedMethods
include SharedMethods
end
Opt 3 - Delegate
If you're using Rails (or just ActiveSupport), you can also make use of the delegate method it adds to class/module.
class A
def not_shared
...
end
def foo
...
end
def bar
...
end
delegate :foo, :bar, to: 'self.class'
end
See here for details:
http://rdoc.info/docs/rails/3.0.0/Module:delegate
You you want to create a module, like shared_methods.rb, you'd put the file in the /lib directory.
You would include the module like this:
class NewClass
include SharedMethods
...
end
I try to do this:
app/models/my_model.rb:
class MyModel < ActiveRecord::Base
include MyModule
...
end
lib/my_module.rb:
module MyModule
before_destroy :my_func #!
def my_func
...
end
end
but I get an error:
undefined method `before_destroy' for MyModule:Module
How can I correct it.
Also I'm new to ruby. What type has these "attributes": before_destroy, validates, has_many?
Are they variables or methods or what?
Thanks
before_destroy, validates, etc. are not attributes or anything like that. These are method calls.
In ruby, the body of a class is all executable code, meaning that each line of the class body is executed by the interpeter just like a method body would.
before_destroy :my_func is a usual ruby method call. The method that gets called is before_destroy, and it receives a symbol :my_func as an argument. This method is looked up in the class (or module) in the scope of which it is called.
So moving on to your question, I think now you should understand that when the interpreter loads your module
module MyModule
before_destroy :my_func #!
def my_func
...
end
end
it starts executing its body and searches for the method before_destroy in this module and cannot find one. What you want to do is call this method not on the module, but rather on the class where the module is included. For that we have a common idiom using the Module#included method:
module MyModule
module InstanceMethods
def my_func
...
end
end
def self.included(base)
base.send :include, InstanceMethods
base.before_destroy :my_func
end
end
In lib/my_module.rb, do this:
class MyInheritedClass < ActiveRecord::Base
before_destroy :my_func
def my_func
...
end
end
In app/models/my_model.rb, do this:
class MyModel < MyInheritedClass
...
end
There is no before_destroy filter in the module you are trying to create above. What my code does is creating a class that will inherit from ActiveRecord::Base and that will be your template class which all your other classes can inherit from. The template class also contains all properties of ActiveRecord::Base.
u can correct this by removing before_destroy from MyModule and place it in MyModel instead
before_destroy and other callbacks are only available to classes which extends ActiveRecord::Base, more info here
hope this helps =)