Extend multiple classes at once - ruby

I want to share a method amongst all Ruby's data object classes. That is to say the following classes:
Hash
String
Number
Array
Firstly, any classes I've missed out?
Secondly, how can I share my_method amongst all classes at once?
class Hash
def my_method
"hi"
end
end
class String
def my_method
"hi"
end
end
class Number
def my_method
"hi"
end
end
class Array
def my_method
"hi"
end
end

This is what modules are for:
module MyModule
def my_method
"hi"
end
end
class Hash
include MyModule
end
class String
include MyModule
end
...
You can also make it shorter with some metaprograming:
[Hash, String, Number, Array].each do |klass|
klass.instance_eval do
include MyModule
end
end
In newer ruby versions (since ruby 2.1.0), include is no longer private method, hence you can simplify the above to:
[Hash, String, Number, Array].each do |klass|
klass.include MyModule
end
Another way to achieve this is to include it within the parent class, like Object:
class Object
def my_method
"hi"
end
end
{}.my_method
This however will add this method to all the classes and will be available everywhere, so be careful.
Regarding whether you missed any classes - you missed tones of it, it all depends on what are you actually trying to achieve here.

Related

How to access class method from the included hook of a Ruby module

I'd like my module to define new instance methods based on its including class' instance methods. But in the included hook, the class methods are not defined yet (as the module is included at the top of the class, before the class methods are defined):
module MyModule
def self.included(includer)
puts includer.instance_methods.include? :my_class_method # false <- Problem
end
end
class MyClass
include MyModule
def my_class_method
end
end
I want the users of the module to be free to include it at the top of their class.
Is there a way to make a module define additional methods to a class?
Note: I don't have to use the included hook if there is another way to achieve this.
There'a a method_added callback you could use:
module MyModule
def self.included(includer)
def includer.method_added(name)
puts "Method added #{name.inspect}"
end
end
end
class MyClass
include MyModule
def foo ; end
end
Output:
Method added :foo
If you want to track both, existing and future methods, you might need something like this:
module MyModule
def self.on_method(name)
puts "Method #{name.inspect}"
end
def self.included(includer)
includer.instance_methods(false).each do |name|
on_method(name)
end
def includer.method_added(name)
MyModule.on_method(name)
end
end
end
Example:
class MyClass
def foo ; end
include MyModule
def bar; end
end
# Method :foo
# Method :bar

Ruby meta-programming - How can I set an instance's attr_accessor value through class method?

I'm trying to write a ruby module with some meta-programming features, but I'm getting a bit confused.
module MetaModule
extend ActiveSupport::Concern
module ClassMethods
def my_method(attribute)
# Define an attr_accessor for the original class
attr_accessor :test_accessor
# This is clearly wrong, but I don't know what is correct
self.test_accessor ||= []
self.test_accessor << attribute
end
end
end
class MyClass
include MetaModule
my_method :name
my_method :age
my_method :city
end
My desired output is: MyClass.new.test_accessor => [:name, :age, :city]
I think there might be a little bit of a mix up here. It is certainly possible to construct a module that will have your desired output, but ultimately it will look something like this
module MetaModule
extend ActiveSupport::Concern
module ClassMethods
def my_method(attribute)
# Define an attr_accessor for the original class
#class_test_accessor ||= []
#class_test_accessor << attribute
end
def class_test_accessor
#class_test_accessor
end
end
def test_accessor
self.class.class_test_accessor
end
end
But you might notice that ultimately we are adding an instance method that simply accesses a class instance variable. Because my_method is a class method, its value wont change per instance. Therefore I would suggest accessing it simply as self.class.class_test_accessor within an instance. If there is something else you were hoping to accomplish w/ my_method (like seed a class_test_accessor and then modify per instance) let me know and I will try to help.

Add class methods through include

I have Ruby class into which I want to include both class and instance methods. Following the pattern described here, I'm currently using the following:
class SomeObject
include SomeObject::Ability
def self.some_builder_method(params)
# use some_class_method ...
end
end
module SomeObject::Ability
module ClassMethods
def some_class_method(param)
# ...
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
def some_instance_method
# ...
end
end
I'd rather not make two separate modules (one being included and the other being extended), because all the methods in my module logically fit together. On the other hand, this pattern a) requires me to define an additional ClassMethods module and b) requires me to write a boilerplate self.included method for every module.
Is there a better way to do this?
Edit 1: I've found another way, but I'm unsure if this is better than the first.
module Concern
def included(base)
# Define instance methods.
instance_methods.each do |m|
defn = instance_method(m)
base.class_eval { define_method(m, defn) }
end
# Define class methods.
(self.methods - Module.methods).each do |m|
unless m == __method__
base.define_singleton_method(m, &method(m))
end
end
end
end
module SomeModule
extend Concern
def self.class_m
puts "Class"
end
def instance_m
puts "Instance"
end
end
class Allo
include SomeModule
end
Allo.class_m # => "Class"
Allo.new.instance_m # => "Instance"
If I understand you correctly, you really just want to use ActiveSupport::Concern:
module PetWorthy
extend ActiveSupport::Concern
included do
validates :was_pet, inclusion: [true, 'yes']
end
def pet #instance method
end
module ClassMethods
def find_petworthy_animal
# ...
end
end
end
class Kitty
include PetWorthy
end
Kitty.find_petworthy_animal.pet
You (hopefully obviously) don't need to use the included method if you don't have any behavior to trigger on include, but I put it in just to demonstrate.

adding class specific functionality in ruby modules

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

Executing a mixin method at the end of a class definition

I have a Mix-in that reflects on the receiver class to generate some code. This means that I need to execute the class method at the end of the class definition, like in this trivially dumbed down example:
module PrintMethods
module ClassMethods
def print_methods
puts instance_methods
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
class Tester
include PrintMethods
def method_that_needs_to_print
end
print_methods
end
I'd like to have the mixin do this for me automatically, but I can't come up with a way. My first thought was to add receiver.print_methods to self.included in the mixin, but that won't work because the method that I want it to reflect on has not been declared yet. I could call include PrintMethods at the end of the class, but that feels like bad form.
Are there any tricks to make this happen so I don't need to call print_methods at the end of the class definition?
First of all, there's no end of class definition. Remember that in Ruby you can reopen the Tester class method after you have 'initialized' it, so the interpreter can't know where the class 'ends'.
The solution I can come up with is to make the class via some helper method, like
module PrintMethods
module ClassMethods
def print_methods
puts instance_methods
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
class Object
def create_class_and_print(&block)
klass = Class.new(&block)
klass.send :include, PrintMethods
klass.print_methods
klass
end
end
Tester = create_class_and_print do
def method_that_needs_to_print
end
end
But certainly having to define classes this way makes my eyes hurt.

Resources