I'd like to have a module which can be included in class, and allow to set options for like this:
class MyService
include Healthcheck
healthcheck_id 'foobar'
end
And module will looks something like:
module Healthcheck
extend ActiveSupport::Concern
included do
def self.healthcheck_id(value)
# What do I do here?
end
end
end
The question is: how do I store this value which was passed as argument, so that I can use it later on?
Maybe some context might help here, I was inspired by Action Mailer:
class ExampleMailer < ActionMailer::Base
default from: "no-reply#example.com"
end
In the example above class method(?) default is accepting hash with arguments, and apparently from is used by Action Mailer when email is being sent.
The pb with #hieu-pham's solution is that you can't define different healthcheck_id values for different classes:
class MyService1
include Healthcheck
healthcheck_id 'foobar_1'
def foo
puts healthcheck_id_value
end
end
class MyService2
include Healthcheck
healthcheck_id 'foobar_2'
def foo
puts healthcheck_id_value
end
end
MyService1.new.foo # foobar_2
MyService2.new.foo # foobar_2
A better solution would be:
module Healthcheck
extend ActiveSupport::Concern
included do
class_attribute :healthcheck_id_value
def self.healthcheck_id(value)
self.healthcheck_id_value = value
end
def self.foo
healthcheck_id_value
end
end
end
class MyService1
include Healthcheck
healthcheck_id 'foobar_1'
end
class MyService2
include Healthcheck
healthcheck_id 'foobar_2'
end
MyService1.foo # foobar_1
MyService2.foo # foobar_2
You can use class variable to do it, so the code would be:
module Healthcheck
extend ActiveSupport::Concern
included do
def self.healthcheck_id(value)
##healthcheck_id_value = value
end
class_eval do
def healthcheck_id_value
self.class.class_variable_get(:##healthcheck_id_value)
end
end
end
end
So from now on you can access healthcheck_id_value, for example:
class MyService
include Healthcheck
healthcheck_id 'foobar'
def foo
puts healthcheck_id_value
end
end
Let's call MyService.new.foo, it will print 'foobar'
Store it in a class variable
##arguments_passed = value
You can look at ActionMailer::Base source code
It uses (class_attribute :default_params)
Related
Currently in my application I have one helper.rb (Helper module is defined in this file) which is included in my controller.rb file like this:
class Controller
before_action :authenticate_user!
include Helper
Problem is that I need to define one more module e.g. Helper2 and I don't know how to include them using if condition and I don't know if even this solution is possible.
example what I want to do:
class Controller
before_action :authenticate_user!
if variable = 1
include Helper
else
include Helper2
end
Thx for answers!
YAGNI.
There are better ways.
The easist way to make the behavior customizable is to just have a set of methods that can be overridden by classes that consume the module:
module Greeter
def initialize(name)
#name = name
end
def salution
"Hello"
end
def hello
"#{salution}!, my name is #{#name}"
end
end
class Person
include Greeter
end
puts Person.new('Bob').hello # Hello!, my name is Bob
class Dog
include Greeter
def salution
"Woof"
end
end
puts Dog.new('Laika').hello # Woof!, my name is Laika
For more complex tasks there is the "macro method" pattern you'll see all over in Ruby:
module Configurable
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
#options ||= {}
end
end
module ClassMethods
def configure(**kwargs)
#options.merge!(kwargs)
end
def options
#options
end
end
end
class Foo
include Configurable
configure(bar: :baz)
end
puts Foo.options.inspect
# {:bar=>:baz}
This is simply a class method that defines class variables / class instance variables, defines methods or whatever you need to be done. For example these very simplefied API clients:
class Client
include HTTParty
format :json
def answers
self.class.get('/answers')
end
end
class StackoverflowClient < Client
base_uri 'https://stackoverflow.com'
end
class SoftwareEngineeringClient < Client
base_uri 'https://softwareengineering.stackexchange.com'
end
Why when I do self.method from a class, I get an undefined method `my_method' for MyModule::MyOtherModule::MyClass:Class
module MyModule
module OtherModule
class MyClass < Base
def my_method
end
def self.my_self_method
my_method
end
end
end
end
I call my_self_method with send from an herited [sic] class:
class Base
class << self
my_method(method_name)
send("my_self_#{method_name}")
end
end
end
I don't understand it.
In your code, you're defining one instance method (my_method), and one class method (my_self_method).
This means you can call:
MyClass.my_self_method
or
MyClass.new.my_method
If you want my_method to be callable from my_self_method, you could define it as:
def self.my_method
...
end
Then the following would be available:
def self.my_self_method
my_method
end
Here's another alternative. There's a comment that suggests it's bad practice to call new.my_method from within a class method, but I've seen a pattern that applies this that I find quite idiomatic, for example:
class MyClass
def self.run(the_variables)
new(the_variables).process
end
def initialize(the_variables)
# setup the_variables
end
def process
# do whatever's needed
end
end
This allows a simple entry point of MyClass.run(the_variables). If your use case seems suitable, a similar pattern for you would be:
module MyModule
module OtherModule
class MyClass < Base
def my_method
end
def self.my_self_method
new.my_method
end
end
end
end
I'm sure there's scope to disagree with this pattern, and would be interested to hear others' opinions in the comments.
Hope this helps clear a few things up #N.Safi.
Is there a way to use arguments when including a ruby module? I have a module Assetable which is included across many classes. I want to be able to generate attr_accessor's on the fly.
module Assetable
extend ActiveSupport::Concern
included do
(argument).times do |i|
attr_accessor "asset_#{i}".to_sym
attr_accessible "asset_#{i}".to_sym
end
end
end
There is a trick: making a class that's inheriting from a module so that you could pass any arguments to the module like class.
class Assetable < Module
def initialize(num)
#num = num
end
def included(base)
num = #num
base.class_eval do
num.times do |i|
attr_accessor "asset_#{i}"
end
end
end
end
class A
include Assetable.new(3)
end
a = A.new
a.asset_0 = 123
a.asset_0 # => 123
The details are blogged at http://kinopyo.com/en/blog/ruby-include-module-with-arguments, hope you'll find it useful.
There is no way of passing arguments when including the module. The best next thing would be to define a class method that lets you create what you need afterwards:
module Assetable
extend ActiveSupport::Concern
module ClassMethods
def total_assets(number)
number.times do |i|
attr_accessor "asset_#{i}"
attr_accessible "asset_#{i}"
end
end
end
end
class C
include Assetable
total_assets 3
end
o = C.new
o.asset_2 = "Some value."
o.asset_2 #=> "Some value."
Also be careful when overriding the included method within a concern because it's also used by ActiveSupport::Concern. You should call super within the overriden method in order to ensure proper initialization.
You can generate and include an anonymous module without polluting global namespaces:
module Assetable
def self.[](argument)
Module.new do
extend ActiveSupport::Concern
included do
(argument).times do |i|
attr_accessor :"asset_#{i}"
attr_accessible :"asset_#{i}"
end
end
end
end
end
class Foo
include Assetable[5]
end
You can't pass arguments to a module. In fact, you can't pass arguments to anything except a message send.
So, you have to use a message send:
module Kernel
private def Assetable(num)
#__assetable_cache__ ||= []
#__assetable_cache__[num] ||= Module.new do
num.times do |i|
attr_accessor :"asset_#{i}"
attr_accessible :"asset_#{i}"
end
end
end
end
class Foo
include Assetable 3
end
Note: I didn't see why you would need ActiveSupport::Concern here at all, but it's easy to add back in.
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
I am using Ruby v1.9.2 and the Ruby on Rails v3.2.2 gem. I had the following module
module MyModule
extend ActiveSupport::Concern
included do
def self.my_method(arg1, arg2)
...
end
end
end
and I wanted to alias the class method my_method. So, I stated the following (not working) code:
module MyModule
extend ActiveSupport::Concern
included do
def self.my_method(arg1, arg2)
...
end
# Note: the following code doesn't work (it raises "NameError: undefined
# local variable or method `new_name' for #<Class:0x00000101412b00>").
def self.alias_class_method(new_name, old_name)
class << self
alias_method new_name, old_name
end
end
alias_class_method :my_new_method, :my_method
end
end
In other words, I thought to extend the Module class someway in order to add an alias_class_method method available throughout MyModule. However, I would like to make it to work and to be available in all my Ruby on Rails application.
Where I should put the file related to the Ruby core extension of the Module class? Maybe in the Ruby on Rails lib directory?
How should I properly "extend" the Module class in the core extension file?
Is it the right way to proceed? That is, for example, should I "extend" another class (Object, BasicObject, Kernel, ...) rather than Module? or, should I avoid implementing the mentioned core extension at all?
But, more important, is there a Ruby feature that makes what I am trying to accomplish so that I don't have to extend its classes?
You could use define_singleton_method to wrap your old method under a new name, like so:
module MyModule
def alias_class_method(new_name, old_name)
define_singleton_method(new_name) { old_name }
end
end
class MyClass
def my_method
puts "my method"
end
end
MyClass.extend(MyModule)
MyClass.alias_class_method(:my_new_method, :my_method)
MyClass.my_new_method # => "my method"
Answering your comment, you wouldn't have to extend every single class by hand. The define_singleton_method is implemented in the Object class. So you could simply extend the Object class, so every class should have the method available...
Object.extend(MyModule)
Put this in an initializer in your Rails app and you should be good to go...
I found an answer on this website: http://engineering.lonelyplanet.com/2012/12/09/monitoring-our-applications-ruby-methods/
The solution is to use class_eval with a block. That enables using variables from the enclosing scope.
module Alias
def trigger
#trigger = true
end
def method_added(name)
if #trigger
#trigger = false
with_x = "#{name}_with_x"
without_x = "#{name}_without_x"
define_method(with_x) do
"#{send(without_x)} with x"
end
alias_method without_x, name
alias_method name, with_x
end
end
def singleton_method_added(name)
if #trigger
#trigger = false
with_x = "#{name}_with_x"
without_x = "#{name}_without_x"
define_singleton_method(with_x) do
"singleton #{send(without_x)} with x"
end
singleton_class.class_eval do
alias_method without_x, name
alias_method name, with_x
end
end
end
end
class TestAlias
extend Alias
trigger
def self.foo
'foo'
end
trigger
def bar
'bar'
end
end
TestAlias.foo # => 'singleton foo with x'
TestAlias.new.bar # => 'bar with x'
If you don't have singleton_class then you should probably upgrade your version of Ruby. If that's not possible you can do this:
class Object
def singleton_class
class << self
self
end
end
end
The accepted answer was confusing and did not work.
class Module
def alias_class_method(new_name, old_name)
define_singleton_method(new_name, singleton_method(old_name))
end
end
module MyModule
def self.my_method
'my method'
end
end
MyModule.alias_class_method(:my_new_method, :my_method)
MyModule.my_new_method # => "my_method"