Rails: callbacks from module - ruby-on-rails

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 =)

Related

Calling a helper from a class and instance method in a Rails model

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

How to override class_method in rails model concern

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/

Run a helper method on a model class method

I created a helper method that I want to run on a model class method and getting a method not found error.
lib/model_helper
module ModelHelper
def method_i_want_to_use
puts "I want to use this method"
end
end
model/foo
class Foo < ActiveRecord::Base
include ModelHelper
def self.bar
method_i_want_to_use
end
end
This setup gives me a no method error.
You have to extend the module instead of include.
extend ModelHelper
include makes the methods available as instance methods of Foo. That means, you can call the method method_i_want_to_use on instances of Foo, not on Foo itself. If you want to call on Foo itself, then use extend.
module ModelHelper
def method_i_want_to_use
puts "I want to use this method"
end
end
class Foo
extend ModelHelper
def self.bar
method_i_want_to_use
end
end
Foo.bar
# >> I want to use this method

Why isn't a module being read by my View-backed Model?

My controller calls the method bar:
class CompsController < ApplicationController
include ApplicationHelper
def quick_create
#var = Matview.bar #projects
end
end
bar is defined in a model that represents a materialized view in my database (it is not in my schema):
class MatView < ActiveRecord::Base
include ApplicationHelper
table_name = 'mat_views'
def self.bar(arg)
foo arg
end
end
'bar' calls method foo, which is defined in my ApplicationHelper:
module ApplicationHelper
def foo(arg1)
#do stuff
end
end
I've included the ApplicationHelper in both my controller and model, and yet I get this error:
NoMethodError in CompsController#quick_create
undefined method `foo' for Matview(Table doesn't exist):Class
why?
Matview.bar #projects
Is calling a class level method on the MatView class.
But your foo and bar are both instance method definitions. To make them class methods, you need def self.bar(arg) or def self.foo(arg1)
And to get class methods into your ActiveRecord model, you need to extend, not include the module:
class MatView < ActiveRecord::Base
extend ApplicationHelper
end
Or, if that does not sound like what you meant to do, then maybe you meant to do:
Matview.new.bar #projects
in which case the instance methods like you wrote them should work.

Accessing other methods in a Ruby module

I am writing my first Rails gem, which adds a method to ActiveRecord. I can't seem to figure out a simple way to call other methods from within the method I am adding to ActiveRecord. Is there a pattern for this I should be using?
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
# This doesn't work
some_utility_method
end
end
def some_utility_method
# Do something useful
end
end
ActiveRecord::Base.send(:include, MyModule)
Once you've included MyModule, ActiveRecord::Base will have my_class_method as a class method (equivalently, an instance method of the Class object ActiveRecord::Base), and some_utility_method as an instance method.
So, inside my_class_method, self is the Class ActiveRecord::Base, not an instance of that class; it does not have some_utility_method as an available method
Edit:
If you want a utility method private to the Module, you could do it like this:
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
# This doesn't work
MyModule::some_utility_method
end
end
def self.some_utility_method
# Do something useful
end
end
ActiveRecord::Base.send(:include, MyModule)

Resources