If I include a module into a class, which has initialize defined, I can call it using super:
module M
def initialize(x)
#m = x
end
end
class MyClass
def initialize
super(3)
end
def val
#m
end
end
MyClass.new.val
# => 3
But how do I code this, if I have several modules, and maybe also a parent class?
class Parent
def initialize(x)
#p = x
end
end
module M
def initialize(x)
#m = x
end
end
module N
def initialize(x)
#n = x
end
end
class MyClass < Parent
include M
include N
def initialize
# ???? How to initialize here?
end
def val
[#m,#n,#p]
end
end
I guess that super(100) within MyClass::initialize would set the variable #n, because N is the "most recent" ancestor, but how do I call the initialize methods in M and Parent?
Take a look at this blog post (http://stdout.koraktor.de/blog/2010/10/13/ruby-calling-super-constructors-from-multiple-included-modules/). It explains how do you use the initialize from different included modules.
Related
I'm working to create a few Ruby builder objects, and thinking on how I could reuse some of Ruby's magic to reduce the logic of the builder to a single class/module. It's been ~10 years since my last dance with the language, so a bit rusty.
For example, I have this builder:
class Person
PROPERTIES = [:name, :age]
attr_accessor(*PROPERTIES)
def initialize(**kwargs)
kwargs.each do |k, v|
self.send("#{k}=", v) if self.respond_to?(k)
end
end
def build
output = {}
PROPERTIES.each do |prop|
if self.respond_to?(prop) and !self.send(prop).nil?
value = self.send(prop)
# if value itself is a builder, evalute it
output[prop] = value.respond_to?(:build) ? value.build : value
end
end
output
end
def method_missing(m, *args, &block)
if m.to_s.start_with?("set_")
mm = m.to_s.gsub("set_", "")
if PROPERTIES.include?(mm.to_sym)
self.send("#{mm}=", *args)
return self
end
end
end
end
Which can be used like so:
Person.new(name: "Joe").set_age(30).build
# => {name: "Joe", age: 30}
I would like to be able to refactor everything to a class and/or module so that I could create multiple such builders that'll only need to define attributes and inherit or include the rest (and possibly extend each other).
class BuilderBase
# define all/most relevant methods here for initialization,
# builder attributes and object construction
end
module BuilderHelper
# possibly throw some of the methods here for better scope access
end
class Person < BuilderBase
include BuilderHelper
PROPERTIES = [:name, :age, :email, :address]
attr_accessor(*PROPERTIES)
end
# Person.new(name: "Joe").set_age(30).set_email("joe#mail.com").set_address("NYC").build
class Server < BuilderBase
include BuilderHelper
PROPERTIES = [:cpu, :memory, :disk_space]
attr_accessor(*PROPERTIES)
end
# Server.new.set_cpu("i9").set_memory("32GB").set_disk_space("1TB").build
I've been able to get this far:
class BuilderBase
def initialize(**kwargs)
kwargs.each do |k, v|
self.send("#{k}=", v) if self.respond_to?(k)
end
end
end
class Person < BuilderBase
PROPERTIES = [:name, :age]
attr_accessor(*PROPERTIES)
def build
...
end
def method_missing(m, *args, &block)
...
end
end
Trying to extract method_missing and build into the base class or a module keeps throwing an error at me saying something like:
NameError: uninitialized constant BuilderHelper::PROPERTIES
OR
NameError: uninitialized constant BuilderBase::PROPERTIES
Essentially the neither the parent class nor the mixin are able to access the child class' attributes. For the parent this makes sense, but not sure why the mixin can't read the values inside the class it was included into. This being Ruby I'm sure there's some magical way to do this that I have missed.
Help appreciated - thanks!
I reduced your sample to the required parts and came up with:
module Mixin
def say_mixin
puts "Mixin: Value defined in #{self.class::VALUE}"
end
end
class Parent
def say_parent
puts "Parent: Value defined in #{self.class::VALUE}"
end
end
class Child < Parent
include Mixin
VALUE = "CHILD"
end
child = Child.new
child.say_mixin
child.say_parent
This is how you could access a CONSTANT that lives in the child/including class from the parent/included class.
But I don't see why you want to have this whole Builder thing in the first place. Would an OpenStruct not work for your case?
Interesting question. As mentioned by #Pascal, an OpenStruct might already do what you're looking for.
Still, it might be more concise to explicitly define the setter methods. It might also be clearer to replace the PROPERTIES constants by methods calls. And since I'd expect a build method to return a complete object and not just a Hash, I renamed it to to_h:
class BuilderBase
def self.properties(*ps)
ps.each do |property|
attr_reader property
define_method :"set_#{property}" do |value|
instance_variable_set(:"##{property}", value)
#hash[property] = value
self
end
end
end
def initialize(**kwargs)
#hash = {}
kwargs.each do |k, v|
self.send("set_#{k}", v) if self.respond_to?(k)
end
end
def to_h
#hash
end
end
class Person < BuilderBase
properties :name, :age, :email, :address
end
p Person.new(name: "Joe").set_age(30).set_email("joe#mail.com").set_address("NYC").to_h
# {:name=>"Joe", :age=>30, :email=>"joe#mail.com", :address=>"NYC"}
class Server < BuilderBase
properties :cpu, :memory, :disk_space
end
p Server.new.set_cpu("i9").set_memory("32GB").set_disk_space("1TB").to_h
# {:cpu=>"i9", :memory=>"32GB", :disk_space=>"1TB"}
I think no need to declare PROPERTIES, we can create a general builder like this:
class Builder
attr_reader :build
def initialize(clazz)
#build = clazz.new
end
def self.build(clazz, &block)
builder = Builder.new(clazz)
builder.instance_eval(&block)
builder.build
end
def set(attr, val)
#build.send("#{attr}=", val)
self
end
def method_missing(m, *args, &block)
if #build.respond_to?("#{m}=")
set(m, *args)
else
#build.send("#{m}", *args, &block)
end
self
end
def respond_to_missing?(method_name, include_private = false)
#build.respond_to?(method_name) || super
end
end
Using
class Test
attr_accessor :x, :y, :z
attr_reader :w, :u, :v
def set_w(val)
#w = val&.even? ? val : 0
end
def add_u(val)
#u = val if val&.odd?
end
end
test1 = Builder.build(Test) {
x 1
y 2
z 3
} # <Test:0x000055b6b0fb2888 #x=1, #y=2, #z=3>
test2 = Builder.new(Test).set(:x, 1988).set_w(6).add_u(2).build
# <Test:0x000055b6b0fb23b0 #x=1988, #w=6>
It's possible to create a Complex number in Ruby using
c = Complex.new(1,2)
but, it can be shortened to
c = Complex(1,2)
Is it possible to achieve the same functionality without having to define a function outside the class, like in the example below?
class Bits
def initialize(bits)
#bits = bits
end
end
def Bits(list) # I would like to define this function inside the class
Bits.new list
end
b = Bits([0,1])
I think Ruby should allow at least one of the proposed constructors below
class Bits
def initialize(bits)
#bits = bits
end
def self.Bits(list) # version 1
new list
end
def Bits(list) # version 2
new list
end
def Bits.Bits(list) # version 3
new list
end
end
Have this snippet:
def make_light_constructor(klass)
eval("def #{klass}(*args) #{klass}.new(*args) end")
end
Now you can do this:
class Test
make_light_constructor(Test)
def initialize(x,y)
print x + y
end
end
t = Test(5,3)
Yes, I know you're still defining a function outside a class - but it is only one function, and now any class you want can make use of its implementation rather than making one function per class.
c = Complex(1,2)
is actually calling a method on Kernel
Basically you can't - the () operator cannot be overriden in Ruby (Complex class is written in C).
You could achieve something similar using []:
class Bits
def self.[](list)
Bits.new list
end
end
Which would allow something like:
b = Bits[[1,2]]
If you pack your classes into some module you can use 2 methods:
self.included - called when you include Mod
self.extend - called when you extend Mod
I have created very basic method using self.included.
Cons: It is hard to write. You can say it is complex; It may not contain all features.
Pros: It looks exactly like Complex(2,3) (it uses () instead of [] as in https://stackoverflow.com/a/24351316/2597260 answer); You create just initialize, self.included create the rest.
module M1
# some random classes
class A; end
class B
def initialize list
#list = list
end
attr_accessor :list
end
class C
def initialize var1
#var1 = var1
end
attr_accessor :var1
end
Answer = 42
# called on `include module_name`
def self.included mod
# classes are constants (in normal cases)
constants.each do |cons|
class_eval do
# I don't like hard-coded `::M1`
klass = ::M1.const_get cons
if klass.class==Class
define_method cons do |*args, &block|
klass.new *args, &block
end
end
end
end
end
end
include M1
p A()
b = B([1,2,3])
p b.list
c = C 42
p c.var1
puts Answer()
# NoMethodError: undefined method `Answer' for main:Object
# thats good, because Answer is not a class!
Here's another hack that you could (but shouldn't) use, inspired by this blog post:
def method_missing(sym, *args, **kwargs, &blk)
Object.const_get(sym).new(*args, **kwargs, &blk)
end
This simply expects any unknown method name to be the name of a class and calls :new on the class.
With rudimentary error handling:
alias sys_method_missing method_missing
def method_missing(sym, *args, **kwargs, &blk)
cls = Object.const_get(sym) if Object.constants.include? sym
if cls.is_a?(Class) then cls.new(*args, **kwargs, &blk)
else sys_method_missing(sym, *args, **kwargs, &blk) end
end
If an unknown method name is the name of a class, this calls :new on the class. Otherwise, it delegates the call to the original implementation of method_missing().
Usage:
class Foo
end
foo = Foo()
p foo
Result:
#<Foo:0x00007f8fe0877180>
In a Ruby class, I'd like to store the value of a variable at the time it includes a given module. Below is a contrived example:
module M
def self.included(base)
base.class_eval do
##inclusion_time = Time.now
def included_at
##inclusion_time
end
end
end
end
class A
include M
end
sleep 3
class B
include M
end
sleep 3
class C
include M
end
a = A.new
b = B.new
c = C.new
puts a.included_at
puts b.included_at
puts c.included_at
I've tried this any number of ways (attr_accessor, set_constant, etc,) but the end result is always the same. All of the classes have whatever value was set last.
How do I work around this?
module M
def self.included _
#inclusion_time = Time.now
end
def included_at
self.class.instance_eval{#inclusion_time}
end
end
I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end
I have several classes and I want each one to maintain on the class level a hash of all the instances that have been created for future lookup. Something akin to:
class A
def initialize(id, otherstuff)
# object creation logic
##my_hash[id]=self
end
def self.find(id)
##my_hash[id]
end
end
so I can then A.find(id) and get the right instance back.
There are several of these classes (A, B, etc), all having ids, all of which I want to have this functionality.
Can I have them all inherit from a superclass which has a generic version of this which they can leverage so I don't have to reimplement many things for every class?
Yes, you can either inherit from the same superclass, or use modules and include:
module M
def initialize(id)
##all ||= {}
##all[id] = self
end
def print
p ##all
end
end
class C
include M
def initialize(id)
super
puts "C instantiated"
end
end
If you want to keep separate indexes for each subclass, you can do something like:
def initialize(id)
##all ||= {}
##all[self.class] ||= {}
##all[self.class][id] = self
end
Edit: After your comment, I see that you need to keep per-class indexes. So:
class A
def initialize(id)
self.class.index(id, self)
end
def self.index id, instance
#all ||= {}
#all[id] = instance
end
def self.find(id)
#all[id]
end
end
class B < A
end
class C < A
end
a = A.new(1)
b = B.new(2)
c = C.new(3)
p A.find(1)
#=> #<A:0x10016c190>
p B.find(2)
#=> #<B:0x10016c140>
p C.find(3)
#=> #<C:0x10016c118>
p A.find(2)
#=> nil