Access a Ruby module's method within same module - ruby-on-rails

I'm new to Ruby and probably don't understand something basic:
I'm trying this:
# lib/common_stuff.rb
module CommonStuff
def self.common_thing
# code
#x = second_thing # --> should access method in same module. Doesn't work.
end
def self.second_thing
# code
end
end
# app/controllers/my_controller.rb
require 'common_stuff'
class MyController < ApplicationController
include CommonStuff
y = self.common_thing # accesses the Module method -> works
end
Error:
NoMethodError (undefined method `second_thing' for
MyController:0x000000088d8990): lib/common.rb:7:in 'common_thing'
I tried both with Module and instance methods. Also declaring only the second_thing an instance method or both methods in the Module as instance methods doesn't work. What do I misunderstand?
** EDIT Corrections**
I realized that my mistake was to make the methods class methods (with self. prefix). Without that it actually works. Thought I tried that, but I must have been blind yesterday. So the working code is (just a constructed example - normally I wouldn't instantiate the controller of course):
# lib/common_stuff.rb
module CommonStuff
def common_thing
#x = second_thing # --> access method in same module. Works now too.
end
def second_thing
10
end
end
# app/controllers/my_controller.rb
require 'common_stuff.rb'
class MyController
include CommonStuff
def a_class
y = common_thing # accesses the Module method -> works
puts y
end
end
ctrl = MyController.new
ctrl.a_class

What do I misunderstand?
1) An #variable is private, so you always need to provide an accessor method to access it (or use instance_variable_get() to violate privacy):
module CommonStuff
def common_thing
#x = second_thing # --> should access method in same module. Doesn't work.
end
def second_thing
10
end
end
class MyController
include CommonStuff
attr_accessor :x
end
obj = MyController.new
obj.common_thing
puts obj.x
--output:--
10
2) You can't include a module's class methods:
module CommonStuff
def self.common_thing
puts 'hello'
#x = second_thing # --> should access method in same module. Doesn't work.
end
def self.second_thing
10
end
end
class MyController
include CommonStuff
end
CommonStuff.common_thing
MyController.common_thing
--output:--
hello
1.rb:21:in `<main>': undefined method `common_thing' for MyController:Class (NoMethodError)
#obj = MyController.new
#obj.common_thing #Same error here
If you want to inject some class methods into MyController, you need to rework your CommonStuff module:
module CommonStuff
def self.included(includer) #Advanced 'hook' method
includer.extend ClassMethods
end
module ClassMethods
def common_thing
puts 'hello'
#x = second_thing # --> should access method in same module. Doesn't work.
end
def second_thing
10
end
end
end
class MyController
include CommonStuff
y = common_thing
puts y
puts instance_variable_get(:#x)
end
--output:--
10
10
The hook method is called whenever the module is included by another class/module, and the method is passed the including class/module as an argument.
Response to commment:
Only a controller's instance variables are made available in a view, e.g.:
class MyController
def do_stuff
#x = 10 #instance variable
end
end
but #variables created inside class methods are not the controller's instance variables:
class MyController
def self.do_stuff
#x = 10
end
end
Therefore, #variables created inside class methods will not be available in the view.

Related

extend self in a module

It seems that this two pieces of codes do the same job, but I would like to understand why I'd rather use one or another
First example:
module MyModule
extend self
def first_method
end
def second_method
end
end
Second example:
module MyModule
def self.first_method
end
def self.second_method
end
end
Your first example defines two instance methods and makes them also available as class (or module) methods via extend:
module MyModule
def first_method; end
def second_method; end
end
MyModule.instance_methods #=> [:second_method, :first_method]
MyModule.methods - Module.methods #=> []
MyModule.extend MyModule
MyModule.instance_methods #=> [:second_method, :first_method]
MyModule.methods - Module.methods #=> [:second_method, :first_method]
Whereas your second example just defines two class (or module) methods and no instance methods:
module MyModule
def self.first_method; end
def self.second_method; end
end
MyModule.instance_methods #=> []
MyModule.methods - Module.methods #=> [:second_method, :first_method]
The first variant can be useful when you want to provide some utility functions that can be called as:
MyModule.first_method
or be included in other modules / classes:
class Foo
include MyModule
def another_method
first_method # <- no explicit receiver needed
end
end
Ruby also provides the helper method module_function to define methods that way:
module MyModule
def first_method
end
module_function :first_method
end
It adds the method as a class methods and makes the instance method private. It's how the methods in Kernel work.

Is there a way to use class method in a module without extend it in rails?

currently I have a module like this:
module MyModule
def A
end
.....
end
and I have a model that I want to use that method A as a class method. However, the thing is I only need that A method. If I extend it, I am gonna extend the other unnecessary class methods into my model. Therefore, is there a way for me to do sth like MyModule.A without rewriting the module like this:
module MyModule
def A
...
end
def self.A
...
end
.....
end
It is kind of repeating myself if I do it that way. I still feel there is a better way to do it in Rails.
Use Module#module_function to make a single function to be a module function:
module M
def m1; puts "m1"; end
def m2; puts "m2"; end
module_function :m2
end
or:
module M
def m1; puts "m1"; end
module_function # from now on all functions are defined as module_functions
def m2; puts "m2"; end
end
M.m1 #⇒ NoMethodError: undefined method `m1' for M:Module
M.m2 #⇒ "m2"
Yes, you can define it as a module_function, then you should be able to access it using module name.
Ex:
module Mod
def my_method
100
end
def self.my_method_1
200
end
module_function :my_method
end
Mod.my_method
# => 100
Mod.my_method_1
# => 200
Note: No need to add the self defined methods in module_function, they are accessible directly. But it's needed for methods defined without self

Call method of included module from class

I have a use case where I have class A which includes module B.
class A
include B
def do_one_thing
# override module's method. do something different instead
end
def do_another_thing
# Call `do_one_thing` from here,
# but call the module's method, not the one I overrode above.
end
end
module B
included do
def do_one_thing
# ...
end
end
# some other methods
end
As shown above, I'm calling do_one_thing from do_another_thing. My problem is that I need to call the module's method (i.e. the super method). Is this possible in Rails?
To property use the included method, you'll need your B module to extend ActiveSupport::Concern but that won't give you the behaviour you want.
If I were you I'd abandon that pattern and use simple native Ruby module patterns:
module B
def do_one_thing
puts 'in module'
# ...
end
# some other methods
end
class A
include B
def do_one_thing
super
puts 'in class'
# override module's method. do something different instead
end
def do_another_thing
do_one_thing
# Call `do_one_thing` from here,
# but call the module's method, not the one I overrode above.
end
end
A.new.do_one_thing
The above code will correctly use the module inheritance you are looking for.
Read more about Ruby module inheritance here
You can 'save' included method before override
module B
extend ActiveSupport::Concern
included do
def do_one_thing
puts 'do_one_thing'
end
end
end
class A
include B
alias_method :old_do_one_thing, :do_one_thing
def do_one_thing
puts "I'd rather do this"
end
def do_another_thing
old_do_one_thing
end
end
a= A.new
a.do_one_thing
a.do_another_thing

Set class variable from module

I want to have separate logs for my app. I created the following module:
module MyApp
module MyLog
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def logger
##logger ||= Logger.new("#{Rails.root}/log/#{self.name.underscore}.log")
end
end
end
end
Then, in any of my models, I can add:
include MyApp::MyLog
and use it as (log file will appear in .../log/cat.log):
Cat.logger.info 'test'
I tried to use this method included on Cat and Dog models, and I have this result:
Cat.new.logger
# => #<Logger:0x007fe4516cf0b0 #progname=nil, ... #dev=#<File:/.../log/cat.log>, ...
Dog.new.logger
# => #<Logger:0x007fe4516cf0b0 #progname=nil, ... #dev=#<File:/.../log/cat.log>, ... (the same)
If I try to use my logger for Dog model first, I will have a log file with the name dog (/dog.log).
How can I set class variable ##logger from a module for each class with the correct initialized logger?
Do not use class variable, use instance_variable that is attached to the class.
module MyApp
module MyLog
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def logger
#logger ||= Logger.new("#{Rails.root}/log/#{self.name.underscore}.log")
end
end
end
end
Example:
module A
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def logger
puts #logger
#logger ||= name
end
end
end
class B
include A
end
class C
include A
end
B.logger
#
B.logger
# B
C.logger
#
B.logger
# B
C.logger
# C
First time you call the method it is nil, thus the empty line, second time you call method the value equals to class name, B, and if called on new class it is again nil, check also this answer
Ruby class instance variable vs. class variable

Rails: dynamically define class method based on parent class name within module/concern

I want to dynamically generate a class method in a Mixin, based on the class name that include this Mixin.
Here is my current code:
module MyModule
extend ActiveSupport::Concern
# def some_methods
# ...
# end
module ClassMethods
# Here is where I'm stuck...
define_method "#{self.name.downcase}_status" do
# do something...
end
end
end
class MyClass < ActiveRecord::Base
include MyModule
end
# What I'm trying to achieve:
MyClass.myclass_status
But this give me the following method name:
MyClass.mymodule::classmethods_status
Getting the base class name inside the method definition works (self, self.name...) but I can't make it works for the method name...
So far, I've tried
define_method "#{self}"
define_method "#{self.name"
define_method "#{self.class}"
define_method "#{self.class.name}"
define_method "#{self.model_name}"
define_method "#{self.parent.name}"
But none of this seems to do the trick :/
Is there any way I can retrieve the base class name (not sure what to call the class that include my module). I've been struggling with this problem for hours now and I can't seem to figure out a clean solution :(
Thanks!
I found a clean solution: using define_singleton_method (available in ruby v1.9.3)
module MyModule
extend ActiveSupport::Concern
included do
define_singleton_method "#{self.name}_status" do
# do stuff
end
end
# def some_methods
# ...
# end
module ClassMethods
# Not needed anymore!
end
end
You can't do it like that - at this point it is not yet known which class (or classes) are including the module.
If you define a self.included method it will be called each time the module is included and the thing doing the including will be passed as an argument. Alternatively since you are using AS::Concern you can do
included do
#code here is executed in the context of the including class
end
You can do something like this:
module MyModule
def self.included(base)
(class << base; self; end).send(:define_method, "#{base.name.downcase}_status") do
puts "Hey!"
end
base.extend(ClassMethods)
end
module ClassMethods
def other_method
puts "Hi!"
end
end
end
class MyClass
include MyModule
end
MyClass.myclass_status
MyClass.other_method
Works for extend:
module MyModule
def self.extended who
define_method "#{who.name.downcase}_status" do
p "Inside"
end
end
end
class MyClass
extend MyModule
end
MyClass.myclass_status

Resources