I'm trying to extend a specific model in my app using railtie. Adding class methods works, but neither does instance methods. I have the following code:
class Railtie
def self.insert
return unless defined?(::ActiveRecord)
::MyApp::MyModel.extend(ModelMethods)
end
end
module ModelMethods
def hello
puts "hello"
end
end
Now, I'm able to call MyModel.hello. But what should I do if i want to add some instance methods? When I try to add them through ::MyApp::MyModel.include(InstanceMethods) it fails with something saying calling a private methods.
include is a private method and cannot have an explicit receiver. You can get around this limitation by using send:
MyModel.send(:include, InstanceMethods)
Related
In a Rails controller you can pass a symbol to the layout method that corresponds to a method in you controller that will return the layout name like this:
layout :my_method
def my_method
'layout_1'
end
I want to have a similar functionality to likewise pass a symbol to my classes method and that class should call the corresponding function and use its return value, like this
myClass.foo :my_method
def my_method
'layout_1'
end
I've read posts[1] that tell me I need to pass
myClass.foo(method(:my_method))
which I find ugly and inconvenient. How is rails here different allowing to pass just the symbol without any wrapper? Can this be achieved like Rails does it?
[1] How to implement a "callback" in Ruby?
If you want to only pass a :symbol into the method, then you have to make assumptions about which method named :symbol is the one you want called for you. Probably it's either defined in the class of the caller, or some outer scope. Using the binding_of_caller gem, we can snag that information easily and evaluate the code in that context.
This surely has security implications, but those issues are up to you! :)
require 'binding_of_caller'
class Test
def foo(sym)
binding.of_caller(1).eval("method(:#{sym})").call
end
end
class Other
def blork
t = Test.new
p t.foo(:bar)
p t.foo(:quxx)
end
def bar
'baz'
end
end
def quxx
'quxx'
end
o = Other.new
o.blork
> "baz"
> "quxx"
I still don't understand, what is author asking about. He's saying about "callbacks", but only wrote how he wants to pass parameter to some method. What that method(foo) should do - i have no idea.
So I tried to predict it's implementation. On class initialising it gets the name of method and create private method, that should be called somewhere under the hood. It possible not to create new method, but store method name in class variable and then call it somewhere.
module Foo
extend ActiveSupport::Concern
module ClassMethods
def foo(method_name)
define_method :_foo do
send method_name
end
end
end
end
class BaseClass
include Foo
end
class MyClass < BaseClass
foo :my_method
private
def my_method
"Hello world"
end
end
MyClass.new.send(:_foo)
#=> "Hello world"
And really, everything is much clearer when you're not just wondering how it works in rails, but viewing the source code: layout.rb
I'm trying to call a method in one controller helper (a module) from another controller helper. It seems to be not possible, even if that method is under the module_function.
I guess I'm missing a fundamental principle in Ruby since I'm pretty newbie. Also it feels like I'm missing the point of how to write right OOP under Rails.
Update: here is an example:
I have FirstController and SecondController, and helper module for each
module FirstHelper
module_function
def methodA
...
end
end
module SecondHelper
def methodB
FirstHelper.methodA
end
end
The call for FirstHelper.methodA from SecondHelper is returning an error:
undefined method `methodA' for SecondHelper:Module
A module is a collection of methods and constants. It basically provides a namespace and prevents name clashes. You need to include or extend your First module inside your Second module.
Include is for adding methods to an instance of a class and Extend is for adding class methods. Read this for more information or this. In your case you can do something like this:
module FirstHelper
def self.methodA
...
end
end
module SecondHelper
include FirstHelper
def methodB
FirstHelper.methodA
end
end
Helper methods are instance methods and cannot be accessed via module, but only vie classes they are included in. All the helpers are included within the view context object, so you should be able to access them simply by name:
module SecondHelper
def methodB
methodA
end
end
use require instead of include it will work
module FirstHelper
class << self
def methodA
...
end
end
end
require 'lib/first_helper'
module SecondHelper
def methodB
FirstHelper.methodA
end
end
I know I will get answers that I shouldn't do this, but due to specific way to solve problem I am facing, I will have to use session in my /lib/example.rb file. (or at least I think I will have to use it)
I am calling an action, which will first run (seudo code):
module ApplicationHelper
def funcion(value)
MyClass.use_this(value)
end
end
And then I will use it in my lib/example.rb
module MyClass
# include SessionsHelper # this is not working
def self.use_this(value)
# I want to be able to use session here. What I need to do that in order to make it work.
session[:my_value] = value
end
end
What should I do in order to use session inside MyClass (I can pass variable to MyClass.use_this(value,session), but I wouldn't want to do that
Edit:
What I want to achieve with this session thing is that I would like to preserve a value during multiple requests. I am making a call to the web application multiple times, and I want to preserve some value on the next call. I am calling the app via API and I shouldn't use database to save values. So I have left with sessions, or text files, or even maybe cookies to make this happen - to preserve the same value on multiple calls.
Why not include the module in your controller, and then call the use_this function directly from there?
module MyClass #should probably rename this anyway
def use_this(value)
session[:my_value] = value
end
end
class SomeController < ApplicationController
include MyClass
def some_action
...
use_this(the_value)
...
end
end
In order to use session inside MyClass may be you could use instance variable #session:
module MyClass
extend SessionsHelper
def self.use_this(value)
#session[:my_value] = value
end
end
module SessionsHelper
def some_method
#session = ...
end
end
self.include(module) method makes the instance methods (and instance variables) of the included module into instance methods of the including module.
Edit: include SessionsHelper changed to extend SessionsHelper
self.extend(module) -- methods of receiver become class methods of that class and instance variables will work between this methods.
I have a small problem that I can't quite get my head around. Since I want to reuse a lot of the methods defined in my Class i decided to put them into an Helper, which I can easily include whenever needed. The basic Class looks like this:
class MyClass
include Helper::MyHelper
def self.do_something input
helper_method(input)
end
end
And here is the Helper:
module Helper
module MyHelper
def helper_method input
input.titleize
end
end
end
Right now I can't call "helper_method" from my Class because of what I think is a scope issue? What am I doing wrong?
I guess that is because self pointer inside of do_something input is InternshipInputFormatter, and not the instance of InternshipInputFormatter. so proper alias to call helper_method(input) will be self.helper_method(input), however you have included the Helper::MyHelper into the InternshipInputFormatter class as an instance methods, not a singleton, so try to extend the class with the instance methods of the module as the signelton methods for the class:
class InternshipInputFormatter
extend Helper::MyHelper
def self.do_something input
helper_method(input)
end
end
InternshipInputFormatter.do_something 1
# NoMethodError: undefined method `titleize' for 1:Fixnum
As you can see, the call has stopped the execution inside the helper_method. Please refer to the document to see the detailed difference between include, and extend.
I've been having this bothering recurring theme; let's just say, I have a class which defines an instance method and a protected class method. The instance method must call the class method. In order to do so, I kind of have to break the visibility rule and use the dangerous 'send' function. Something like this:
class Bang
def instance_bang
self.class.send(:class_band)
end
protected
def self.class_bang
puts "bang"
end
end
I find this awful, since the class method should be used inside the class scope, therefore should remain visible and callable within it, right? Is there an alternative way to use class methods in instance methods with needing to rely on the "send" function and therefore not break visibility?
UPDATE:
Following Sergio Tulentsev's response (thx for the correction), I'll update my concern with a code snippet that sums up my concerns of the method visibility being taken into account while still inside the scope where it has been defined.
class Bang
def instance_bang
private_bang = 1
self.private_bang(private_bang)
end
private
def private_bang(p)
puts "bang"
p
end
end
Calling Bang.new.instance_bang will raise an Exception unless you use send on that private_bang call (this time I checked it :) ).
EDIT: Answering the updated question
It is forbidden to call private methods with explicit receiver. You either have to use implicit receiver (private_bang, without self) or use send. Please see my another answer for more information.
By the way, the original question is about calling class instance methods from instance methods. Your clarification doesn't include that. But if that's still true, you have to use self.class.send or make the method public (so that you can use explicit receiver).
Let's consider a private class method (since protected class methods don't make sense).
We know it's possible for an instance to call a private method on itself, as long as it isn't using an explicit receiver (self.call_something_private). It seems you also expect that an instance can call a private class method on its own class, but that is not the case.
Let's look at a way to do this without using send.
The private and protected macros only affect instance methods of the current scope, not class methods. Here are three ways to rewrite your original code:
class Bang
def instance_bang
self.class.class_bang
end
# declare method visibility after
def self.class_bang
puts "bang"
end
private_class_method :class_bang
# inline
private_class_method def self.class_bang
puts "bang"
end
# class scope
class << self
# the private macro works here because we're inside the class scope
private
def class_bang
puts "bang"
end
end
end
So now we want to expose an interface on the class to call class_bang, but only if it's called by an instance of Bang.
class Bang
def instance_bang
self.class.invoke_class_bang(self)
end
class << self
private
def class_bang
puts "bang"
end
public
# we ask the receiver to pass itself as an argument ...
def invoke_class_bang(receiver)
# ... so that we can check whether it's
class_bang if receiver.is_a?(Bang)
end
end
end
That's not a very nice looking solution though. Here's a sneakier way:
class Bang
def initialize
def self.instance_bang() self.class.method(:class_bang).call end
end
class << self
private
def class_bang
puts "bang"
end
end
end
"The class method should be used inside the class scope, therefore should remain visible and callable within it, right?" Yes, that's correct, and that's the behavior Ruby exhibits. (As a point of clarification, instance scope is not "within" class scope. They are, appropriately, separate.)
The non-send solution is to subclass or reopen the class to add a public class method to access the protected class method.