In Ruby I want to have a class include a series of modules and have these individual modules execute a block or method (or just find some way to edit an instance variable) when initializing that class.
I know I can do this by creating a method in the module and then calling it in the class' initialize method, but I want some way to do this by simply including the module and calling one method to execute any code the modules add to initialize, that way I can have a large amount of things included in a class without worrying about adding a line of code in the initialize method for every single module included.
I've checked out aliasing, super, and related things but haven't gotten anything...
If it helps to understand what I'm hoping to accomplish here's some pseudocode:
module Mod1
call_this_block_on_initialize { #a.push 4 }
end
module Mod2
call_this_block_on_initialize { #a.push 5 }
end
class Test
attr_accessor :a
include Mod1
include Mod2
def initialize
#a = [1, 2, 3]
call_those_blocks_set_by_mods
end
end
t = Test.new
t.a # returns [1, 2, 3, 4, 5]
This may be a bit wordy but I think the title sums up what I'm trying to do. Thanks for any help!
There are a few ways you can do this. This example will redefine the initialize method and add whatever extra code you want:
module MyModule
def self.included(base) # built-in Ruby hook for modules
base.class_eval do
original_method = instance_method(:initialize)
define_method(:initialize) do |*args, &block|
original_method.bind(self).call(*args, &block)
#a.push 4 # (your module code here)
end
end
end
end
class Test
attr_accessor :a
def initialize
#a = [1, 2, 3]
end
# It must be included after the `initialize`
# definition or the module won't find the method:
include MyModule
end
However:
I think what you really want is subclassing. If you have a lot of classes with similar behavior, as it seems you do, ask yourself if there is a natural abstract parent class. Can you explain what you did with super and why it didn't work?
Related
Given the following program, in which I want to:
Create a Struct with some keys
Provide some default customization
Allow a block to be passed for further customization
module Magic
def self.MagicStruct(keys_array, &block)
Struct.new(*keys_array) do
#keys = keys_array
def self.magic_class_method
puts "constructed with #{#keys}"
end
def magic_instance_method
puts "instance method"
end
# --- DOESN'T WORK, CONTEXT IS OUTSIDE OF MODULE --- #
# yield if block_given?
instance_eval(&block) if block_given?
end
end
end
Foo = Magic.MagicStruct([:a, :b, :c]) do
puts "class customizations executing..."
def self.custom_class_method
puts "custom class method"
end
def custom_instance_method
puts "custom instance method"
end
end
Foo.magic_class_method # works
Foo.custom_class_method # works
x = Foo.new({a: 10, b: 20, c: 30})
x.magic_instance_method # works
x.custom_instance_method # fails
Output:
class customizations executing...
constructed with [:a, :b, :c]
custom class method
instance method
Traceback (most recent call last):
`<main>': undefined method `custom_instance_method' for #<struct Foo a={:a=>10, :b=>20, :c=>30}, b=nil, c=nil> (NoMethodError)
Why is the self.custom_class_method correctly added to the Foo class, but the custom_instance_method is not? This usage is clearly stated in the Struct documentation, so I'm afraid there's some kind of scoping or context issue I'm missing here.
I would prefer to keep the nice def method() ... end syntax rather than resorting to having a strict requirement to use define_method("method") in the customization block, which does happen to work.
In Ruby there is the notion of a current class which is the target of keywords such as def.
When you use instance_eval, the current class is set to self.singleton_class. In other words, def x and def self.x are equivalent. In your code, custom_instance_method is defined on the singleton class of the newly created Struct, making it a class method.
When you use class_eval, the current class is set to self. Since this method is only available on classes, it will set the current class to the one you called the method on. In other words, def x will define a method that's available to all objects of that class. This is what you wanted.
For more details, see my other answer.
This is a gem I wrote that I believe does most, if not all that you want (not so sure about adding class methods though, I'd have to play with that). While the Struct created is immutable, you can have a look at the code and alter it to meet your needs; it's pretty simple. What you'd be interested in, would be here, which basically amounts to what you see below. The extension of the modules uses instance_eval in a way that I believe is what you want as well:
# https://github.com/gangelo/immutable_struct_ex/blob/main/lib/immutable_struct_ex.rb
# Defines the methods used to create/manage the ImmutableStructEx struct.
module ImmutableStructEx
class << self
# Most likely changing this method name to #create in subsequent version, but alas...
def new(**hash, &block)
Struct.new(*hash.keys, keyword_init: true, &block).tap do |struct|
return struct.new(**hash).tap do |struct_object|
struct_object.extend Comparable
struct_object.extend Immutable
end
end
end
end
end
Usage details can be found in the README.md in the github repository.
I hope this help, at least in part.
I have troubles with understanding the global area of visibility in Ruby, so, I know that you cant use Module methods in your own class for example:
module Mod
def self.meth
“module method”
end
end
class Klass
include Mod
end
p Klass.meth
# Error
but when i knew that you can do such thing:
include Math
p sin 2
#0.909....
I was confused, because i thought you cant use module methods in any class without calling the method name. Also i had a supposition, that module Math has instance methods, like Kernel, but, unfortunately, no. Now i am doubting, that I understood such methods like extend and include correctly, so, could you please explain to me this thing and what will happen if we will change include to extend
You have encounter a weirdness of module_function: https://apidock.com/ruby/Module/module_function/
module Foo
def foo # this is (irb) 2
end
end
Foo.singleton_methods #=> []
Foo.instance_methods #=> [:foo]
Foo.instance_method(:foo).source_location #=> ["(irb)", 2]
module Foo
module_function :foo # this is (irb) 9
end
Foo.singleton_methods #=> [:foo]
Foo.singleton_method(:foo).source_location #=> ["(irb)", 2]
Foo.instance_methods #=> []
Foo.private_instance_methods #=> [:foo]
Foo.instance_method(:foo).source_location #=> ["(irb)", 2]
So, module_function takes an instance method of the module, makes it private and copies it onto a singleton class.
module_function can also be used without the method name, which works a bit like private method, modifying all the future methods added to this module.
Math is a full module_function module, meaning that the methods are defined both as singletons and private instance methods, which is why you can use it both ways.
I've put some functionality in a module, to be extended by an object. I'd like the functionality to be executed automatically when the module is extended. However, it has to be executed in the context of the instance, not Module.
module X
extend self
#array = [1,2,3]
end
obj.extend(X)
Currently, #array does not get created in the instance. I don't wish to force the developer to call some initialization method, since then for each Module he needs to know the name of a unique method to call. Is this possible ?
You can use Module#extended hook for execution code on extension and BasicObject#instance_exec (or BasicObject#instance_eval) for executing arbitrary code in context of extended object:
module X
def self.extended(obj)
obj.instance_exec { #array = [1,2,3] }
end
end
class O
attr_reader :array
end
obj = O.new
obj.array # => nil
obj.extend(X)
obj.array # => [1, 2, 3]
Show this article
module Math
def self.extended(base)
# Initialize module.
end
end
Here is what I tried:
module A
def self.method1; "method1"; end
def method2; "method2"; end
end
module B; include A; end
B.method1 # => error
B.method2 # => error
B::method1 # => error
B::method2 # => error
I want to avoid copying and pasting equivalent code between two modules. The reason I'm using modules instead of classes here is because I don't need more than one instance of each module, as they simply hold constants (other modules, at this point).
What is the best way to solve this problem?
Plain include only gives you instance methods (method2 in your particular piece of code). If you want to share module-level methods - extract them to separate module and extend other modules with it:
module A
extend self # to be able to use A.method1
def method1
"method1"
end
end
module B
extend A
end
B.method1 # => "method1"
It is also possible get module-level methods by include, but with a little twist, using hook method:
module A
def self.included(other)
other.extend ModuleMethods # this is where the magic happens
end
def instance_method
'instance method'
end
module ModuleMethods
def module_method
'module method'
end
end
extend ModuleMethods # to be able to use A.module_method
end
module B
include A
end
B.module_method #=> "module method"
B.instance_methods #=> [:instance_method]
First of all, please note that A.method2 won't work either. You can create objects including A (or B) that will have method2:
class C
include B # (or A)
end
c = C.new
c.method2
So, for method2 it just works as you intended.
Regarding method1, it is a singleton method of the object A and there is no way to inherit it.
Background:
I have a module which declares a number of instance methods
module UsefulThings
def get_file; ...
def delete_file; ...
def format_text(x); ...
end
And I want to call some of these methods from within a class. How you normally do this in ruby is like this:
class UsefulWorker
include UsefulThings
def do_work
format_text("abc")
...
end
end
Problem
include UsefulThings brings in all of the methods from UsefulThings. In this case I only want format_text and explicitly do not want get_file and delete_file.
I can see several possible solutions to this:
Somehow invoke the method directly on the module without including it anywhere
I don't know how/if this can be done. (Hence this question)
Somehow include Usefulthings and only bring in some of it's methods
I also don't know how/if this can be done
Create a proxy class, include UsefulThings in that, then delegate format_text to that proxy instance
This would work, but anonymous proxy classes are a hack. Yuck.
Split up the module into 2 or more smaller modules
This would also work, and is probably the best solution I can think of, but I'd prefer to avoid it as I'd end up with a proliferation of dozens and dozens of modules - managing this would be burdensome
Why are there lots of unrelated functions in a single module? It's ApplicationHelper from a rails app, which our team has de-facto decided on as the dumping ground for anything not specific enough to belong anywhere else. Mostly standalone utility methods that get used everywhere. I could break it up into seperate helpers, but there'd be 30 of them, all with 1 method each... this seems unproductive
I think the shortest way to do just throw-away single call (without altering existing modules or creating new ones) would be as follows:
Class.new.extend(UsefulThings).get_file
If a method on a module is turned into a module function you can simply call it off of Mods as if it had been declared as
module Mods
def self.foo
puts "Mods.foo(self)"
end
end
The module_function approach below will avoid breaking any classes which include all of Mods.
module Mods
def foo
puts "Mods.foo"
end
end
class Includer
include Mods
end
Includer.new.foo
Mods.module_eval do
module_function(:foo)
public :foo
end
Includer.new.foo # this would break without public :foo above
class Thing
def bar
Mods.foo
end
end
Thing.new.bar
However, I'm curious why a set of unrelated functions are all contained within the same module in the first place?
Edited to show that includes still work if public :foo is called after module_function :foo
Another way to do it if you "own" the module is to use module_function.
module UsefulThings
def a
puts "aaay"
end
module_function :a
def b
puts "beee"
end
end
def test
UsefulThings.a
UsefulThings.b # Fails! Not a module method
end
test
If you want to call these methods without including module in another class then you need to define them as module methods:
module UsefulThings
def self.get_file; ...
def self.delete_file; ...
def self.format_text(x); ...
end
and then you can call them with
UsefulThings.format_text("xxx")
or
UsefulThings::format_text("xxx")
But anyway I would recommend that you put just related methods in one module or in one class. If you have problem that you want to include just one method from module then it sounds like a bad code smell and it is not good Ruby style to put unrelated methods together.
To invoke a module instance method without including the module (and without creating intermediary objects):
class UsefulWorker
def do_work
UsefulThings.instance_method(:format_text).bind(self).call("abc")
...
end
end
Not sure if someone still needs it after 10 years but I solved it using eigenclass.
module UsefulThings
def useful_thing_1
"thing_1"
end
class << self
include UsefulThings
end
end
class A
include UsefulThings
end
class B
extend UsefulThings
end
UsefulThings.useful_thing_1 # => "thing_1"
A.new.useful_thing_1 # => "thing_1"
B.useful_thing_1 # => "thing_1"
Firstly, I'd recommend breaking the module up into the useful things you need. But you can always create a class extending that for your invocation:
module UsefulThings
def a
puts "aaay"
end
def b
puts "beee"
end
end
def test
ob = Class.new.send(:include, UsefulThings).new
ob.a
end
test
A. In case you, always want to call them in a "qualified", standalone way (UsefulThings.get_file), then just make them static as others pointed out,
module UsefulThings
def self.get_file; ...
def self.delete_file; ...
def self.format_text(x); ...
# Or.. make all of the "static"
class << self
def write_file; ...
def commit_file; ...
end
end
B. If you still want to keep the mixin approach in same cases, as well the one-off standalone invocation, you can have a one-liner module that extends itself with the mixin:
module UsefulThingsMixin
def get_file; ...
def delete_file; ...
def format_text(x); ...
end
module UsefulThings
extend UsefulThingsMixin
end
So both works then:
UsefulThings.get_file() # one off
class MyUser
include UsefulThingsMixin
def f
format_text # all useful things available directly
end
end
IMHO it's cleaner than module_function for every single method - in case want all of them.
As I understand the question, you want to mix some of a module's instance methods into a class.
Let's begin by considering how Module#include works. Suppose we have a module UsefulThings that contains two instance methods:
module UsefulThings
def add1
self + 1
end
def add3
self + 3
end
end
UsefulThings.instance_methods
#=> [:add1, :add3]
and Fixnum includes that module:
class Fixnum
def add2
puts "cat"
end
def add3
puts "dog"
end
include UsefulThings
end
We see that:
Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
#=> [:add2, :add3, :add1]
1.add1
2
1.add2
cat
1.add3
dog
Were you expecting UsefulThings#add3 to override Fixnum#add3, so that 1.add3 would return 4? Consider this:
Fixnum.ancestors
#=> [Fixnum, UsefulThings, Integer, Numeric, Comparable,
# Object, Kernel, BasicObject]
When the class includes the module, the module becomes the class' superclass. So, because of how inheritance works, sending add3 to an instance of Fixnum will cause Fixnum#add3 to be invoked, returning dog.
Now let's add a method :add2 to UsefulThings:
module UsefulThings
def add1
self + 1
end
def add2
self + 2
end
def add3
self + 3
end
end
We now wish Fixnum to include only the methods add1 and add3. Is so doing, we expect to get the same results as above.
Suppose, as above, we execute:
class Fixnum
def add2
puts "cat"
end
def add3
puts "dog"
end
include UsefulThings
end
What is the result? The unwanted method :add2 is added to Fixnum, :add1 is added and, for reasons I explained above, :add3 is not added. So all we have to do is undef :add2. We can do that with a simple helper method:
module Helpers
def self.include_some(mod, klass, *args)
klass.send(:include, mod)
(mod.instance_methods - args - klass.instance_methods).each do |m|
klass.send(:undef_method, m)
end
end
end
which we invoke like this:
class Fixnum
def add2
puts "cat"
end
def add3
puts "dog"
end
Helpers.include_some(UsefulThings, self, :add1, :add3)
end
Then:
Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
#=> [:add2, :add3, :add1]
1.add1
2
1.add2
cat
1.add3
dog
which is the result we want.
After almost 9 years here's a generic solution:
module CreateModuleFunctions
def self.included(base)
base.instance_methods.each do |method|
base.module_eval do
module_function(method)
public(method)
end
end
end
end
RSpec.describe CreateModuleFunctions do
context "when included into a Module" do
it "makes the Module's methods invokable via the Module" do
module ModuleIncluded
def instance_method_1;end
def instance_method_2;end
include CreateModuleFunctions
end
expect { ModuleIncluded.instance_method_1 }.to_not raise_error
end
end
end
The unfortunate trick you need to apply is to include the module after the methods have been defined. Alternatively you may also include it after the context is defined as ModuleIncluded.send(:include, CreateModuleFunctions).
Or you can use it via the reflection_utils gem.
spec.add_dependency "reflection_utils", ">= 0.3.0"
require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions
This old question comes to me today when I am studing Ruby and found interesting so I want to answer with my new knowlege.
Assume that you have the module
module MyModule
def say
'I say'
end
def cheer
'I cheer'
end
end
then with the class so call Animal I can take cheer method from MyModule as following
class Animal
define_method(:happy, MyModule.method(:cheer))
end
This is so called unbound method, so you can take a callable object and bind it to another place(s).
From this point, you can use the method as usual, such as
my_dog = Animal.new
my_dog.happy # => "I cheer"
Hope this help as I also learned something new today.
To learn further, you can use irb and take a look at Method object.