Ruby Syntax to Populate a new Object's Instance Variables [duplicate] - ruby

I find myself using hash arguments to constructors quite a bit, especially when writing DSLs for configuration or other bits of API that the end user will be exposed to. What I end up doing is something like the following:
class Example
PROPERTIES = [:name, :age]
PROPERTIES.each { |p| attr_reader p }
def initialize(args)
PROPERTIES.each do |p|
self.instance_variable_set "##{p}", args[p] if not args[p].nil?
end
end
end
Is there no more idiomatic way to achieve this? The throw-away constant and the symbol to string conversion seem particularly egregious.

You don't need the constant, but I don't think you can eliminate symbol-to-string:
class Example
attr_reader :name, :age
def initialize args
args.each do |k,v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
end
#=> nil
e1 = Example.new :name => 'foo', :age => 33
#=> #<Example:0x3f9a1c #name="foo", #age=33>
e2 = Example.new :name => 'bar'
#=> #<Example:0x3eb15c #name="bar">
e1.name
#=> "foo"
e1.age
#=> 33
e2.name
#=> "bar"
e2.age
#=> nil
BTW, you might take a look (if you haven't already) at the Struct class generator class, it's somewhat similar to what you are doing, but no hash-type initialization (but I guess it wouldn't be hard to make adequate generator class).
HasProperties
Trying to implement hurikhan's idea, this is what I came to:
module HasProperties
attr_accessor :props
def has_properties *args
#props = args
instance_eval { attr_reader *args }
end
def self.included base
base.extend self
end
def initialize(args)
args.each {|k,v|
instance_variable_set "##{k}", v if self.class.props.member?(k)
} if args.is_a? Hash
end
end
class Example
include HasProperties
has_properties :foo, :bar
# you'll have to call super if you want custom constructor
def initialize args
super
puts 'init example'
end
end
e = Example.new :foo => 'asd', :bar => 23
p e.foo
#=> "asd"
p e.bar
#=> 23
As I'm not that proficient with metaprogramming, I made the answer community wiki so anyone's free to change the implementation.
Struct.hash_initialized
Expanding on Marc-Andre's answer, here is a generic, Struct based method to create hash-initialized classes:
class Struct
def self.hash_initialized *params
klass = Class.new(self.new(*params))
klass.class_eval do
define_method(:initialize) do |h|
super(*h.values_at(*params))
end
end
klass
end
end
# create class and give it a list of properties
MyClass = Struct.hash_initialized :name, :age
# initialize an instance with a hash
m = MyClass.new :name => 'asd', :age => 32
p m
#=>#<struct MyClass name="asd", age=32>

The Struct clas can help you build such a class. The initializer takes the arguments one by one instead of as a hash, but it's easy to convert that:
class Example < Struct.new(:name, :age)
def initialize(h)
super(*h.values_at(:name, :age))
end
end
If you want to remain more generic, you can call values_at(*self.class.members) instead.

There are some useful things in Ruby for doing this kind of thing.
The OpenStruct class will make the values of a has passed to its initialize
method available as attributes on the class.
require 'ostruct'
class InheritanceExample < OpenStruct
end
example1 = InheritanceExample.new(:some => 'thing', :foo => 'bar')
puts example1.some # => thing
puts example1.foo # => bar
The docs are here:
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/ostruct/rdoc/OpenStruct.html
What if you don't want to inherit from OpenStruct (or can't, because you're
already inheriting from something else)? You could delegate all method
calls to an OpenStruct instance with Forwardable.
require 'forwardable'
require 'ostruct'
class DelegationExample
extend Forwardable
def initialize(options = {})
#options = OpenStruct.new(options)
self.class.instance_eval do
def_delegators :#options, *options.keys
end
end
end
example2 = DelegationExample.new(:some => 'thing', :foo => 'bar')
puts example2.some # => thing
puts example2.foo # => bar
Docs for Forwardable are here:
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/forwardable/rdoc/Forwardable.html

Given your hashes would include ActiveSupport::CoreExtensions::Hash::Slice, there is a very nice solution:
class Example
PROPERTIES = [:name, :age]
attr_reader *PROPERTIES #<-- use the star expansion operator here
def initialize(args)
args.slice(PROPERTIES).each {|k,v| #<-- slice comes from ActiveSupport
instance_variable_set "##{k}", v
} if args.is_a? Hash
end
end
I would abstract this to a generic module which you could include and which defines a "has_properties" method to set the properties and do the proper initialization (this is untested, take it as pseudo code):
module HasProperties
def self.has_properties *args
class_eval { attr_reader *args }
end
def self.included base
base.extend InstanceMethods
end
module InstanceMethods
def initialize(args)
args.slice(PROPERTIES).each {|k,v|
instance_variable_set "##{k}", v
} if args.is_a? Hash
end
end
end

My solution is similar to Marc-André Lafortune. The difference is that each value is deleted from the input hash as it is used to assign a member variable. Then the Struct-derived class can perform further processing on whatever may be left in the Hash. For instance, the JobRequest below retains any "extra" arguments from the Hash in an options field.
module Message
def init_from_params(params)
members.each {|m| self[m] ||= params.delete(m)}
end
end
class JobRequest < Struct.new(:url, :file, :id, :command, :created_at, :options)
include Message
# Initialize from a Hash of symbols to values.
def initialize(params)
init_from_params(params)
self.created_at ||= Time.now
self.options = params
end
end

Please take a look at my gem, Valuable:
class PhoneNumber < Valuable
has_value :description
has_value :number
end
class Person < Valuable
has_value :name
has_value :favorite_color, :default => 'red'
has_value :age, :klass => :integer
has_collection :phone_numbers, :klass => PhoneNumber
end
jackson = Person.new(name: 'Michael Jackson', age: '50', phone_numbers: [{description: 'home', number: '800-867-5309'}, {description: 'cell', number: '123-456-7890'})
> jackson.name
=> "Michael Jackson"
> jackson.age
=> 50
> jackson.favorite_color
=> "red"
>> jackson.phone_numbers.first
=> #<PhoneNumber:0x1d5a0 #attributes={:description=>"home", :number=>"800-867-5309"}>
I use it for everything from search classes (EmployeeSearch, TimeEntrySearch) to reporting ( EmployeesWhoDidNotClockOutReport, ExecutiveSummaryReport) to presenters to API endpoints. If you add some ActiveModel bits you can easily hook these classes up to forms for gathering criteria. I hope you find it useful.

Related

Ruby metaprogramming: accessing class name

I'm fairly new to Ruby metaprogramming. I'm trying to write code which generates the
"dup" function for a class when it's created, using a list of fields which should be passed into the constructor. However, I can't figure out how to get access to the name of the class I'm creating, while I'm creating it.
So for example, if I had this code:
class Example
make_dup :name, :value
attr_accessor :name, :value
def initialize(name,value)
#name, #value = name, value
end
end
I'd want it to create the method:
def dup
Example.new(name,value)
end
I'm just getting stuck on how it would figure out to insert Example there.
Note that all classes have built-in dup and clone methods. You can customize what happens in them by adding an initialize_copy method, e.g.:
class Foo
attr_accessor :bar
def initialize_copy(orig)
super
#bar = #bar.dup
end
end
In case that isn't what you're truly looking for, you can access an object's class using its class method:
class Foo
def p_class
p self.class # Foo.new.p_class => Foo ; self is *a* `Foo'
end
def self.p_class
p self.class # Foo.p_class => Class ; self *is* `Foo'
end
end
def dup
self.class.new(name,value)
end
Maybe you can implement it this way:
module MyDup
def make_dup(*args)
define_method(:my_dup) do
obj = self.class.new(nil, nil)
args.each do |arg|
obj.send(arg.to_s + "=", self.send(arg))
end
obj
end
end
end
class Example
extend MyDup
make_dup :name, :value
attr_accessor :name, :value
def initialize(name,value)
#name, #value = name, value
end
end
e = Example.new("John", 30)
p e
d = e.my_dup
p d
Execution result as follows:
#<Example:0x000000022325d8 #name="John", #value=30>
#<Example:0x00000002232358 #name="John", #value=30>

Get all instance variables declared in class

Please help me get all instance variables declared in a class the same way instance_methods shows me all methods available in a class.
class A
attr_accessor :ab, :ac
end
puts A.instance_methods #gives ab and ac
puts A.something #gives me #ab #ac...
You can use instance_variables:
A.instance_variables
but that’s probably not what you want, since that gets the instance variables in the class A, not an instance of that class. So you probably want:
a = A.new
a.instance_variables
But note that just calling attr_accessor doesn’t define any instance variables (it just defines methods), so there won’t be any in the instance until you set them explicitly.
a = A.new
a.instance_variables #=> []
a.ab = 'foo'
a.instance_variables #=> [:#ab]
If you want to get all instances variables values you can try something like this :
class A
attr_accessor :foo, :bar
def context
self.instance_variables.map do |attribute|
{ attribute => self.instance_variable_get(attribute) }
end
end
end
a = A.new
a.foo = "foo"
a.bar = 42
a.context #=> [{ :#foo => "foo" }, { :#bar => 42 }]
It's not foolproof - additional methods could be defined on the class that match the pattern - but one way I found that has suited my needs is
A.instance_methods.grep(/[a-z_]+=/).map{ |m| m.to_s.gsub(/^(.+)=$/, '#\1') }
If you want to get a hash of all instance variables, in the manner of attributes, following on from Aschen's answer you can do
class A
attr_accessor :foo, :bar
def attributes
self.instance_variables.map do |attribute|
key = attribute.to_s.gsub('#','')
[key, self.instance_variable_get(attribute)]
end.to_h
end
end
a = A.new
a.foo = "foo"
a.bar = 42
a.context #=> {'foo' => 'foo', 'bar' => 42}
Building on the answer from #Obromios , I added .to_h and .to_s to a class to allow for pleasant, flexible dumping of attributes suitable for display to an end user.
This particular class (not an ActiveRecord model) will have a variety of attributes set in different situations. Only those attribs that have values will appear when printing myvar.to_s, which was my desire.
class LocalError
attr_accessor :product_code, :event_description, :error_code, :error_column, :error_row
def to_h
instance_variables.map do |attribute|
key = attribute.to_s.gsub('#', '')
[key, self.instance_variable_get(attribute)]
end.to_h
end
def to_s
to_h.to_s
end
end
This allows me to put this simple code in a mailer template:
Data error: <%= #data_error %>
And it produces (for example):
Data error: {"event_description"=>"invalid date", "error_row"=>13}
This is nice, as the mailer doesn't have to be updated as the LocalError attributes change in the future.

Turning constructor arguments into instance variables [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Idiomatic object creation in ruby
There are many occaisions when I have an initialize method that looks like this:
class Foo
def initialize bar, buz, ...
#bar, #buz, ... = bar, buz, ...
end
end
Is there a way to do this with a simple command like:
class Foo
attr_constructor :bar, :buz, ...
end
where the symbols represent the name of the instance variables (with the spirit/flavor of attr_accessor, attr_reader, attr_writer)?
I was wondering if there is a built in way or a more elegant way of doing something like this:
class Class
def attr_constructor *vars
define_method("initialize") do |*vals|
vars.zip(vals){|var, val| instance_variable_set("##{var}", val)}
end
end
end
so that I can use it like this:
class Foo
attr_constructor :foo, :bar, :buz
end
p Foo.new('a', 'b', 'c') # => #<Foo:0x93f3e4c #foo="a", #bar="b", #buz="c">
p Foo.new('a', 'b', 'c', 'd') # => #<Foo:0x93f3e4d #foo="a", #bar="b", #buz="c">
p Foo.new('a', 'b') # => #<Foo:0x93f3e4e #foo="a", #bar="b", #buz=nil>
I'd use OpenStruct:
require 'ostruct'
class Foo < OpenStruct
end
f = Foo.new(:bar => "baz")
f.bar
#=> "baz"
Edit: Ah OK, sorry misunderstood you. How about just:
class Foo
def initialize(*args)
#baz, #buz = args
end
end
Would this work for you?
class Foo
def initialize(hash)
hash.each { |k,v| instance_variable_set("##{k}", v) }
end
end
Interesting question. A little meta-programming should take care of it.
module Attrs
def self.included(base)
base.extend ClassMethods
base.class_eval do
class << self
attr_accessor :attrs
end
end
end
module ClassMethods
# Define the attributes that each instance of the class should have
def has_attrs(*attrs)
self.attrs = attrs
attr_accessor *attrs
end
end
def initialize(*args)
raise ArgumentError, "You passed too many arguments!" if args.size > self.class.attrs.size
# Loop through each arg, assigning it to the appropriate attribute (based on the order)
args.each_with_index do |val, i|
attr = self.class.attrs[i]
instance_variable_set "##{attr}", val
end
end
end
class Foo
include Attrs
has_attrs :bar, :buz
end
f = Foo.new('One', 'Two')
puts f.bar
puts f.buz
Of course the downside to this is inflexibility - you have to pass your constructor arguments in a specific order. Of course that's how most programming languages are. Rails people might argue you should instead do
f = Foo.new(:bar => 'One', :baz => 'Two')
which would allow you to pass in attrs in any order, as well as strip away most of the meta-programming. But that is a lot more to type.

Declaring instance variables iterating over a hash!

i want to do the following:
I want to declare the instance variables of a class iterating over a dictionary.
Let's assume that i have this hash
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
and i want to have each key as instance variable of a class. I want to know if i could declare the variables iterating over that hash. Something like this:
class MyClass
def initialize()
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
hash.each do |k,v|
#k = v
end
end
end
I know this doesn't work! I only put this piece of code to see if you could understand what i want more clearly.
Thanks!
class MyClass
def initialize()
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
hash.each do |k,v|
instance_variable_set("##{k}",v)
# if you want accessors:
eigenclass = class<<self; self; end
eigenclass.class_eval do
attr_accessor k
end
end
end
end
The eigenclass is a special class belonging just to a single object, so methods defined there will be instance methods of that object but not belong to other instances of the object's normal class.
class MyClass
def initialize
# define a hash and then
hash.each do |k,v|
# attr_accessor k # optional
instance_variable_set(:"##{k}", v)
end
end
end
Chuck's answer is better than my last two attempts. The eigenclass is not self.class like I had thought; it took a better test than I had written to realize this.
Using my old code, I tested in the following manner and found that the class was indeed manipulated and not the instance:
a = MyClass.new :my_attr => 3
b = MyClass.new :my_other_attr => 4
puts "Common methods between a & b:"
c = (a.public_methods | b.public_methods).select { |v| a.respond_to?(v) && b.respond_to?(v) && !Object.respond_to?(v) }
c.each { |v| puts " #{v}" }
The output was:
Common methods between a & b:
my_other_attr=
my_attr
my_attr=
my_other_attr
This clearly disproves my presupposition. My apologies Chuck, you were right all along.
Older answer:
attr_accessor only works when evaluated in a class definition, not the initialization of an instance. Therefore, the only method to directly do what you want is to use instance_eval with a string:
class MyClass
def initialize(params)
#hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
params.each do |k,v|
instance_variable_set("##{k}", v)
instance_eval %{
def #{k}
instance_variable_get("##{k}")
end
def #{k}= (new_val)
instance_variable_set("##{k}", new_val)
end
}
end
end
end
To test this try:
c = MyClass.new :my_var => 1
puts c.my_var
http://facets.rubyforge.org/apidoc/api/more/classes/OpenStructable.html
OpensStructable is a mixin module
which can provide OpenStruct behavior
to any class or object. OpenStructable
allows extention of data objects with
arbitrary attributes.

Best Way to Abstract Initializing Attributes

What's the best way to abstract this pattern:
class MyClass
attr_accessor :foo, :bar
def initialize(foo, bar)
#foo, #bar = foo, bar
end
end
A good solution should take superclasses into consideration and be able to handle still being able to have an initializer to do more things. Extra points for not sacrificing performance in your solution.
A solution to that problem already (partially) exists, but if you want a more declarative approach in your classes then the following should work.
class Class
def initialize_with(*attrs, &block)
attrs.each do |attr|
attr_accessor attr
end
(class << self; self; end).send :define_method, :new do |*args|
obj = allocate
init_args, surplus_args = args[0...attrs.size], args[attrs.size..-1]
attrs.zip(init_args) do |attr, arg|
obj.instance_variable_set "##{attr}", arg
end
obj.send :initialize, *surplus_args
obj
end
end
end
You can now do:
class MyClass < ParentClass
initialize_with :foo, :bar
def initialize(baz)
#initialized = true
super(baz) # pass any arguments to initializer of superclass
end
end
my_obj = MyClass.new "foo", "bar", "baz"
my_obj.foo #=> "foo"
my_obj.bar #=> "bar"
my_obj.instance_variable_get(:#initialized) #=> true
Some characteristics of this solution:
Specify constructor attributes with initialize_with
Optionally use initialize to do custom initialization
Possible to call super in initialize
Arguments to initialize are the arguments that were not consumed by attributes specified with initialize_with
Easily extracted into a Module
Constructor attributes specified with initialize_with are inherited, but defining a new set on a child class will remove the parent attributes
Dynamic solution probably has performance hit
If you want to create a solution with absolute minimal performance overhead, it would be not that difficult to refactor most of the functionality into a string which can be evaled when the initializer is defined. I have not benchmarked what the difference would be.
Note: I found that hacking new works better than hacking initialize. If you define initialize with metaprogramming, you'd probably get a scenario where you pass a block to initialize_with as a substitute initializer, and it's not possible to use super in a block.
This is the first solution that comes to my mind. There's one big downside in my module: you must define the class initialize method before including the module or it won't work.
There's probably a better solution for that problem, but this is what I wrote in less than a couple of minutes.
Also, I didn't keep performances too much into consideration. You probably can find a much better solution than me, especially talking about performances. ;)
#!/usr/bin/env ruby -wKU
require 'rubygems'
require 'activesupport'
module Initializable
def self.included(base)
base.class_eval do
extend ClassMethods
include InstanceMethods
alias_method_chain :initialize, :attributes
class_inheritable_array :attr_initializable
end
end
module ClassMethods
def attr_initialized(*attrs)
attrs.flatten.each do |attr|
attr_accessor attr
end
self.attr_initializable = attrs.flatten
end
end
module InstanceMethods
def initialize_with_attributes(*args)
values = args.dup
self.attr_initializable.each do |attr|
self.send(:"#{attr}=", values.shift)
end
initialize_without_attributes(values)
end
end
end
class MyClass1
attr_accessor :foo, :bar
def initialize(foo, bar)
#foo, #bar = foo, bar
end
end
class MyClass2
def initialize(*args)
end
include Initializable
attr_initialized :foo, :bar
end
if $0 == __FILE__
require 'test/unit'
class InitializableTest < Test::Unit::TestCase
def test_equality
assert_equal MyClass1.new("foo1", "bar1").foo, MyClass2.new("foo1", "bar1").foo
assert_equal MyClass1.new("foo1", "bar1").bar, MyClass2.new("foo1", "bar1").bar
end
end
end
class MyClass < Struct.new(:foo, :bar)
end
I know this is an old question with perfectly acceptable answers but I wanted to post my solution as it takes advantage of Module#prepend (new in Ruby 2.2) and the fact that modules are also classes for very simple solution. First the module to make the magic:
class InitializeWith < Module
def initialize *attrs
super() do
define_method :initialize do |*args|
attrs.each { |attr| instance_variable_set "##{attr}", args.shift }
super *args
end
end
end
end
Now let's use our fancy module:
class MyClass
prepend InitializeWith.new :foo, :bar
end
Note that I left our the attr_accessible stuff as I consider that a separate concern although it would be trivial to support. Now I can create an instance with:
MyClass.new 'baz', 'boo'
I can still define an initialize for custom initialization. If my custom initialize take an argument those will be any extra arguments provided to the new instance. So:
class MyClass
prepend InitializeWith.new :foo, :bar
def initialize extra
puts extra
end
end
MyClass.new 'baz', 'boo', 'dog'
In the above example #foo='baz', #bar='boo' and it will print dog.
What I also like about this solution is that it doesn't pollute the global namespace with a DSL. Objects that want this functionality can prepend. Everybody else is untouched.
This module allows an attrs hash as an option to new(). You can include the module in a class with inheritance, and the constructor still works.
I like this better than a list of attr values as parameters, because, particularly with inherited attrs, I wouldn't like trying to remember which param was which.
module Attrize
def initialize(*args)
arg = args.select{|a| a.is_a?(Hash) && a[:attrs]}
if arg
arg[0][:attrs].each do |key, value|
self.class.class_eval{attr_accessor(key)} unless respond_to?(key)
send(key.to_s + '=', value)
end
args.delete(arg[0])
end
(args == []) ? super : super(*args)
end
end
class Hue
def initialize(transparent)
puts "I'm transparent" if transparent
end
end
class Color < Hue
include Attrize
def initialize(color, *args)
p color
super(*args)
p "My style is " + #style if #style
end
end
And you can do this:
irb(main):001:0> require 'attrize'
=> true
irb(main):002:0> c = Color.new("blue", false)
"blue"
=> #<Color:0x201df4>
irb(main):003:0> c = Color.new("blue", true, :attrs => {:style => 'electric'})
"blue"
I'm transparent
"My style is electric"

Resources