Having a hard time figuring this out. Suppose I wanted to write a module, and when included, it would allow classes to define methods by calling a method with symbols
class Anything
include Foo
initializers :hello, :goodbye
end
module Foo
# What goes in here? Its not
# def self.initializers(*symbols)
end
Same syntax idea as attr_accessible. Tried finding it in the Rails source, but, well..
module Foo
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def initializers *names
names.each do |name|
define_method name do
'ok'
end
end
end
end
def self.included(base)
base.extend ClassMethods
end
end
class Anything
include Foo
initializers :hello, :goodbye
end
puts Anything.new.hello #=> ok
for example:
module Foo
def self.included(base)
block = Proc.new do |*symbols|
puts symbols.inspect
end
base.class.send(:define_method, :initializers, block)
end
end
class Anything
include Foo
initializers :one, :two , :three
end
Related
So while I'm all against extending existing classes this way, sometimes (hacking rspec) it's necessary to do something like:
module MyModule
module ClassMethods
def define_something(name)
##names ||= []
##names << name
end
end
def self.included(base)
base.extend ClassMethods
end
def all_names
##names
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names
and then it yields this error:
NameError: uninitialized class variable ##names in MyModule
and I understand that because at the time of writing MyModule::ClassMethods - we are working on instance not class (not self.), so I tried:
module MyModule
module ClassMethods
def define_something(name)
#names ||= []
#names << name
end
end
def self.included(base)
base.extend ClassMethods
end
def all_names
##names
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names
it does not work either, finally I ended up with:
module MyModule
module ClassMethods
def define_something(name)
#names << name
end
end
def self.included(base)
base.instance_variable_set(:#names, [])
base.send(:define_method, :all_names) { base.instance_variable_get(:#names) }
base.extend ClassMethods
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names
Is there a better way to do that?
It's usually better from a design perspective to avoid crossing the class/instance line using instance variable references. This is usually more clear:
module MyModule
module ClassMethods
def define_something(name)
self.defined_somethings << name
end
def defined_somethings
#_my_module_names ||= []
end
end
def self.included(base)
base.extend ClassMethods
end
def all_names
self.class.defined_somethings
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names.inspect
#=> ["one","two"]
I've taken care here to create a class-level instance variable with a verbose name. Calling it #name could put it into conflict with a variable defined by the class that includes this module. Remember, when designing mixin code you're a guest and you need to be extra polite.
Class-type instance variables like ##name are sometimes trouble because they can end up spanning inheritance chains depending on how they're used. Defining a method means you can override it at any point in the chain, something not possible with a shared variable.
In other words, treat the instance's class as a separate object and make clear, well-defined method calls to maintain that separation.
Try the following
module MyModule
module ClassMethods
def define_something(name)
#names ||= []
#names << name
end
end
def self.included(base)
base.extend ClassMethods
end
def all_names
self.class.instance_variable_get(:#names)
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names
A class variable can be accessed using ## operator from a class level method i.e. self. methods.
#tadman's answer has a generic approach for similar problems (generally found in gems that provide modules to implement a functionality)
This my implementation to developing way to run code before all method in your model
The call "before_hook :months_used" method need to be on bottom of class to the ExecutionHooks can get the instance_method loaded in the module. I would like to load the instance methods on top
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_used
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end
You can do this with prepend. prepend is like include in that it adds a module to the ancestors of the class, however instead of adding it after the class it adds it before.
This means that if a method exists both in the prepended module and the class then the module implementation is called first (and it can optionally call super if it wants to call the base class).
This allows you to write a hooks module like so:
module Hooks
def before(*method_names)
to_prepend = Module.new do
method_names.each do |name|
define_method(name) do |*args, &block|
puts "before #{name}"
super(*args,&block)
end
end
end
prepend to_prepend
end
end
class Example
extend Hooks
before :foo, :bar
def foo
puts "in foo"
end
def bar
puts "in bar"
end
end
In real use you would probably want to stash that module somewhere so that each call to before doesn't create a new module but that is just an inplementation detail
#rathrio This is my implementation using method_added that you talked. Thanks
module ExecutionHooks
def validation
p "works1"
end
def self.included(base)
base.send :extend, ClassMethods
end
end
module ClassMethods
attr_writer :hooked
def hooked
#hooked ||= []
end
def method_added(method)
return if #hooks.nil?
return unless #hooks.include?(method)
m = self.instance_method(method)
unless hooked.include?(method)
hooked << method
define_method(method) do |*args, &block|
validation
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
end
def hooks
#hooks ||= []
end
end
end
class BalanceChart < BalanceFind
include ExecutionHooks
before_hook :months_data, :months_used, :debits_amount, :test
def test
"test"
end
end
Instead of redefining the method when calling before_hook, you could override the method_added hook to prepend your before hooks to a method right after it was defined. This way your before_hook calls can be (actually, must be) placed at the top of the class definition.
Newbie question:
I know how include and extend work, what I am wondering is if there is a way to get both class and instance methods from a single module?
This is how I do it with two modules:
module InstanceMethods
def mod1
"mod1"
end
end
module ClassMethods
def mod2
"mod2"
end
end
class Testing
include InstanceMethods
extend ClassMethods
end
t = Testing.new
puts t.mod1
puts Testing::mod2
Thanks for taking your time ...
There is a common idiom for that. It makes use of included object model hook. This hook gets invoked every time when a module is included to a module/class
module MyExtensions
def self.included(base)
# base is our target class. Invoke `extend` on it and pass nested module with class methods.
base.extend ClassMethods
end
def mod1
"mod1"
end
module ClassMethods
def mod2
"mod2"
end
end
end
class Testing
include MyExtensions
end
t = Testing.new
puts t.mod1
puts Testing::mod2
# >> mod1
# >> mod2
I personally like to group instance method to a nested module as well. But this is less accepted practice, as far as I know.
module MyExtensions
def self.included(base)
base.extend ClassMethods
base.include(InstanceMethods)
# or this, if you have an old ruby and the line above doesn't work
# base.send :include, InstanceMethods
end
module InstanceMethods
def mod1
"mod1"
end
end
module ClassMethods
def mod2
"mod2"
end
end
end
module Foo
def self.included(m)
def m.show1
p "hi"
end
end
def show2
p "hello"
end
end
class Bar
include Foo
end
Bar.new.show2 #=> "hello"
Bar.show1 #=> "hi"
Yes. It's exactly as simple as you would expect, due to the genius of ruby:
module Methods
def mod
"mod"
end
end
class Testing
include Methods # will add mod as an instance method
extend Methods # will add mod as a class method
end
t = Testing.new
puts t.mod
puts Testing::mod
Or, you could do:
module Methods
def mod1
"mod1"
end
def mod2
"mod2"
end
end
class Testing
include Methods # will add both mod1 and mod2 as instance methods
extend Methods # will add both mod1 and mod2 as class methods
end
t = Testing.new
puts t.mod1
puts Testing::mod2
# But then you'd also get
puts t.mod2
puts Testing::mod1
possibly I'm not explaining the concept very well, but I'm looking to add class methods to a series of ruby classes to enable them to hold class specific information which will then be called by individual instance methods of the classes.
I can make it work, but it is a bit ugly. Can anyone as it requires 2 modules, one included and the other extended (see example code below).
Can anyone think of a more elegant way of implementing this functionality ?
Thanks
Steve
This module is extended to give class methods but adding an instance member to each class it is included in
module My1
def my_methods (*sym_array)
#my_methods=sym_array
end
def method_list
#my_methods
end
end
This module is included to give instance methods
module My2
def foo
self.class.method_list.each { |m| self.send m }
end
end
Now use the modules - the ugliness is having to use an include and extend statement to allow me to pass a set of symbols to a class method which will then be implemented in an
instance
class Foo
extend My1
include My2
my_methods :baz
def baz
puts "Baz!"
end
end
class Bar
extend My1
include My2
my_methods :frodo
def frodo
puts "Frodo!"
end
end
class Wibble < Bar
extend My1
include My2
my_methods :wobble
def wobble
puts "Wobble!"
end
end
Here is the required output - note that each class has its own instance #my_methods so the behaviour is different for the derived class Wibble < Bar
f=Foo.new
b=Bar.new
w=Wibble.new
f.foo #=> "Bar!"
b.foo #=> "Frodo!"
w.foo #=> "Wobble!"
When a module is included, a hook is called on it. You can use that to do the extend you want.
module M1
def self.included(base)
base.extend(M2)
end
end
People often call that second module M1::ClassMethods. If you're using rails, ActiveSupport::Concern encapsulates this pattern
I would suggest to use a hook from module instead:
module MyModule
def self.included(klass)
klass.extend ClassMethods
end
def foo
self.class.method_list.each{ |m| self.send m }
end
module ClassMethods
attr_reader :method_list
def my_methods(*sym_array)
#method_list = sym_array
end
end
end
So it simplifies to call include only a module whenever you want the functionality to given classes.
class Foo
include MyModule
my_methods :baz
def baz
puts "Baz!"
end
end
I have a module like the following
module MyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def foo
##var = 1
end
def bar
puts ##var
end
end
end
class A
include MyModule
foo
end
class B < A; end
so that
B.bar outputs '1'.
However, I would like to have .bar only be defined if .foo is called. I tried
module MyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def foo
##var = 1
extend SingletonMethods
end
module SingletonMethods
def bar
puts ##var
end
end
end
The problem is that
B.bar
returns the error "uninitialized class variable ##var in MyModule::SingletonMethods". How can I make it so that a variable defined in .foo is available to .bar?
use mattr_accessor instead
I was able to access class variables from a module using the self syntax
class User
include Base
##attributes = [:slug, :email, :crypted_password]
end
module Base
def self.included(base)
base.extend ClassMethods
end
def headers
if defined? self.attributes
self.attributes
end
end
end
Now calling User.headers gives me the expected result of
[:slug, :email, :crypted_password]
If anyone can shed more light on why this works exactly so in ruby, please let me know!