Set class variable from module - ruby-on-rails

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

Related

Share data between methods

I have this module, which gets included in a class:
module MyModule
def self.included base
base.extend ClassMethods
end
module ClassMethods
def my_module_method data
include MyModule::InstanceMethods
after_save :my_module_process
attr_accessor :shared_data
shared_data = data
# instance_variable_set :#shared_data, data
end
end
module InstanceMethods
private
def my_module_process
raise self.shared_data.inspect
# raise instance_variable_get(:#shared_data).inspect
end
end
end
I want to use the data (parameter) passed to my_module_method within my_module_process. I've used attr_accessor as well as instance variables, but either of them return nil.
Since you're using rails, your module can be greatly simplified by making it a AS::Concern
module MyModule
extend ActiveSupport::Concern
included do
# after_save :my_module_process # or whatever
cattr_accessor :shared_data
end
module ClassMethods
def my_module_method(data)
self.shared_data = data
end
end
def my_module_process
"I got this shared data: #{self.class.shared_data}"
end
end
The key points here are:
cattr_accessor, which is similar to attr_accessor, but defines class-level methods
self.class.shared_data to access that class-level data from the instances.
Usage:
class Foo
include MyModule
end
f = Foo.new
f.my_module_process # => "I got this shared data: "
Foo.my_module_method({foo: 'bar'})
f.my_module_process # => "I got this shared data: {:foo=>\"bar\"}"
I've used attr_accessor as well as instance variables, but either of them return nil.
In ruby, it is super-important to know what is self at any given moment. This is what defines the methods and instance variables available to you. As an exercise, I offer you to find out, why user.name returns nil here (and how to fix it).
class User
#name = 'Joe'
def name
#name
end
end
user = User.new
user.name # => nil

Cannot use instance variables in prepended module

I want to be able to include my module in ActiveRecord::Base so that the has_folder_attachments method is available to my Rails AR classes.
I'm doing this to extend the original module's function to support AR hooks; however the variables #physical_path and #dice are both nil and I don't understand why.
module FolderAttachments
module ClassMethods
def has_folder_attachments(physical_path, excludes: [])
#physical_path = physical_path
super
end
end
def self.prepended(base)
class << base
prepend ClassMethods
end
end
attr_reader :physical_path
end
module ActiveRecord
class Base
prepend FolderAttachments
attr_reader :dice
# This should run after the module method
def self.has_folder_attachments(*args)
#dice = true
end
end
end
class Damned < ActiveRecord::Base
has_folder_attachments :for_real
end
damn = Damned.new
puts damn.physical_path # => nil
puts damn.dice # => nil
You are mixing instance and (meta)class context when using the two variables. Both variables are set their values in methods that are run in the class context (more precisely in the context of the metaclass). Thus, you cannot access these variables (and their attr_readers) in an instance context.
For the attr_readers to work, you have to move them to the class context and access them from there:
module FolderAttachments
module ClassMethods
...
attr_reader :physical_path
end
end
module ActiveRecord
class Base
...
class << self
attr_reader :dice
end
end
end
damn = Damned.new
damn.class.physical_path # => :for_real
damn.class.dice # => true
Or you may also add instance-level readers that delegate to the class-level readers so that you can access them also in instance context:
module FolderAttachments
module ClassMethods
...
attr_reader :physical_path
end
def physical_path
self.class.physical_path
end
end
module ActiveRecord
class Base
...
class << self
attr_reader :dice
end
def dice
self.class.dice
end
end
end
damn = Damned.new
damn.physical_path # => :for_real
damn.dice # => true

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

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)

including Modules in controller

I have done a module in lib directory in ruby on rails application
its like
module Select
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def select_for(object_name, options={})
#does some operation
self.send(:include, Selector::InstanceMethods)
end
end
I called this in a controller like
include Selector
select_for :organization, :submenu => :general
but I want to call this in a function
i.e
def select
#Call the module here
end
Let's clarify: You have a method defined in a module, and you want that method to be used in an instance method.
class MyController < ApplicationController
include Select
# You used to call this in the class scope, we're going to move it to
# An instance scope.
#
# select_for :organization, :submenu => :general
def show # Or any action
# Now we're using this inside an instance method.
#
select_for :organization, :submenu => :general
end
end
I'm going to change your module slightly. This uses include instead of extend. extend is for adding class methods, and include it for adding instance methods:
module Select
def self.included(base)
base.class_eval do
include InstanceMethods
end
end
module InstanceMethods
def select_for(object_name, options={})
# Does some operation
self.send(:include, Selector::InstanceMethods)
end
end
end
That will give you an instance method. If you want both instance and class methods, you just add the ClassMethods module, and use extend instead of include:
module Select
def self.included(base)
base.class_eval do
include InstanceMethods
extend ClassMethods
end
end
module InstanceMethods
def select_for(object_name, options={})
# Does some operation
self.send(:include, Selector::InstanceMethods)
end
end
module ClassMethods
def a_class_method
end
end
end
Does that clear things up? In your example you defined a module as Select but included Selector in your controller...I just used Select in my code.

Resources