I update this question to better reflect what I have problems to grasp. The example below kind of work but how can I access the Sub class then I have defined it inside the Base class? Should it not be better to do the call outside the class? If so how do I do that? The second question I have in this example is how to grab values so I can use them in another class. Here I store the values in an array that I later need to unpack in another class. Should I not be able to use a proc for this?
Basically what I want to do is to sort the methods into two different classes depending on if they are nested or not.
class Sub
def initialize(base_class_method)
#base_class_method = base_class_method
#sub_methods = []
end
# omitted code here
def base_class_method
#base_class_method
end
def sub_actions(method)
#sub_methods << method
end
def return_sub_methods
#sub_methods
end
def method_missing(sub_method, &block)
if sub_method
sub_method
else
super
end
end
end
class Base
def initialize
#base_methods = []
end
# omitted code here
def base_actions(method)
#base_methods << method
end
def return_base_methods
#base_methods
end
def method_missing(method, &block)
if block_given?
Sub.new(method).instance_eval(&block)
elsif method
base_actions(method)
else
super
end
end
end
base = Base.new
base.instance_eval do
something1
something_with_a_block do
something_inside_block1_1
something_inside_block1_2
end
something2
something_with_a_block2_2 do
something_inside_block2_1
end
end
p base.return_base_methods #=> [:something1, :something2] works!
You can do something like this.
class Test
# reserved method to instantiate object
def initialize(a,b,c)
#a = a
#b = b
#c = c
end
# getters
def a
#a
end
def b
#b
end
def c
#c
end
def abc
[#a, #b, #c] # returns an array
end
# setters
def a=(var)
#a = var
end
def b=(var)
#b = var
end
def c=(var)
#c = var
end
# set values all at once
def update(a, b, c)
#a = a
#b = b
#c = c
end
end
z = Test.new('something','something','something')
z.update('something!','nothing!',"a thing!")
z.a
z.b
z.c
z.a = 'wow, new value!'
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>
I have a ruby class, and in one of the methods, it calls an external function, and pass in all instance variables, and continue with the return value. Here is the code:
class MyClass
attr_accessor :name1
attr_accessor :name2
...
attr_accessor :namen
def inner_func():
all_vars = ???? # how to collect all my instance variables into a dict/Hash?
res = out_func(all_vars)
do_more_stuff(res)
end
end
The problem is the instance variables might vary in subclasses. I can't refer them as their names. So, is there a way to do this? Or Am I thinking in a wrong way?
You can use instance_variables to collect them in an Array. You will get all initialized instance variables.
class MyClass
attr_accessor :name1
attr_accessor :name2
...
attr_accessor :namen
def inner_func():
all_vars = instance_variables
res = out_func(all_vars)
do_more_stuff(res)
end
end
You could keep track of all accessors as you create them:
class Receiver
def work(arguments)
puts "Working with #{arguments.inspect}"
end
end
class MyClass
def self.attr_accessor(*arguments)
super
#__attribute_names__ ||= []
#__attribute_names__ += arguments
end
def self.attribute_names
#__attribute_names__
end
def self.inherited(base)
parent = self
base.class_eval do
#__attribute_names__ = parent.attribute_names
end
end
def attributes
self.class.attribute_names.each_with_object({}) do |attribute_name, result|
result[attribute_name] = public_send(attribute_name)
end
end
def work
Receiver.new.work(attributes)
end
attr_accessor :foo
attr_accessor :bar
end
class MySubclass < MyClass
attr_accessor :baz
end
Usage
my_class = MyClass.new
my_class.foo = 123
my_class.bar = 234
my_class.work
# Working with {:foo=>123, :bar=>234}
my_subclass = MySubclass.new
my_subclass.foo = 123
my_subclass.bar = 234
my_subclass.baz = 345
my_subclass.work
# Working with {:foo=>123, :bar=>234, :baz=>345}
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
not quite understanding factory method here...
here is the respec line:
Temperature.from_celsius(50).in_celsius.should == 50
Here is what I have now:
getting errors...not quite sure how to satisfy this. thanks
class Temperature
attr_accessor :f
attr_accessor :c
def initialize(args)
#f = args[:f]
#c = args[:c]
end
def in_fahrenheit
#f or
(#c*9.0/5.0)+32
end
def in_celsius
#c or
(#f-32)*(5.0/9.0)
end
def self.from_celsius(c)
new c
end
end
This should help
class Temperature
def initialize c
#c = c
end
def in_celsius
#c
end
def in_fahrenheit
#c *9.0 /5.0 +32
end
# factory pattern typically instantiates a new object
def self.from_celsius(c)
new c
end
end
puts Temperature.from_celsius(50).in_celsius #=> 50
puts Temperature.from_celsius(100).in_fahrenheit #=> 212
I would recommend against attr_accessor :c unless you want users to have public access to temp.c. Without it, users will be forced to use temp.in_celsius or temp.in_fahrenheit
You need to assign to :c in the initialize method. Then you need self.from_celsius to return a new instance of Temperature. You probably want something like this:
class Temperature
attr_accessor :c
def initialize c
#c = c
end
def in_celsius
#c
end
def in_fahrenheit
9/5 * #c + 32
end
def self.from_celsius(num)
Temperature.new(num)
end
def self.from_fahrenheit(num)
Temperature.new((num-32)*5/9)
end
end
Now rspec shows true
1.9.1p378 :047 > Temperature.from_celsius(50).in_celsius.should == 50
=> true
1.9.1p378 :131 > Temperature.from_fahrenheit(32).in_celsius.should == 0
=> true
The reason you're getting "error: Can't covert symbol to integer –" is because you're in your Temperature.from_celsius(50) you're passing it an integer when you're supposed to pass it a key & symbol for the options hash.
initialized
class Temperature
def initialize(opts = {})
#options = opts
end
class factory method
def self.from_celsius(x)
Temperature.new(:c => x)
end
instance method
def in_celsius
if #options[:c] == nil
return (#options[:f]-32) * (5/9.to_f)
else
return #options[:c]
end
end
I tried to to extend the code from this question for keeping records of an attribute value. However, my code fails in the case of more than one attributes. Here is the code:
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name
ah=attr_name+"_history"
attr_reader ah
class_eval %Q{
def #{attr_name}= (attr_name)
#attr_name=attr_name
if #ah == nil
#ah=[nil]
end
#ah.push(attr_name)
end
def #{ah}
#ah
end
def #{attr_name}
#attr_name
end
}
end
end
Here a dummy class for testing
class Foo
attr_accessor_with_history :bar
attr_accessor_with_history :bar1
end
f = Foo.new
f.bar = 1
f.bar = 2
f.bar1 = 5
p f.bar_history
p f.bar1_history
For some reason, f.bar and f.bar1 both return 5 and f.bar_history = f.bar1_history = [nil, 1, 2, 5]. Any idea why that is?
You were using #ah and #attr_name instead of ##{ah} and ##{attr_name} when getting/setting in the methods. This meant that they were always setting and returning the same instance variable, instead of different, dynamically named ones.
class Class
def attr_accessor_with_history(attr_name)
class_eval %{
attr_reader :#{attr_name}, :#{attr_name}_history
def #{attr_name}=(value)
##{attr_name} = value
##{attr_name}_history ||= [nil]
##{attr_name}_history << value
end
}
end
end
I've also generally cleaned up your code a little to make it (I think) clearer and more concise.