Is there a way to initialize an object through a hash? - ruby

If I have this class:
class A
attr_accessor :b,:c,:d
end
and this code:
a = A.new
h = {"b"=>10,"c"=>20,"d"=>30}
is it possible to initialize the object directly from the hash, without me needing to go over each pair and call instance_variable_set? Something like:
a = A.new(h)
which should cause each instance variable to be initialized to the one that has the same name in the hash.

You can define an initialize function on your class:
class A
attr_accessor :b,:c,:d
def initialize(h)
h.each {|k,v| public_send("#{k}=",v)}
end
end
Or you can create a module and then "mix it in"
module HashConstructed
def initialize(h)
h.each {|k,v| public_send("#{k}=",v)}
end
end
class Foo
include HashConstructed
attr_accessor :foo, :bar
end
Alternatively you can try something like constructor

OpenStructis worth considering:
require 'ostruct' # stdlib, no download
the_hash = {"b"=>10, "c"=>20, "d"=>30}
there_you_go = OpenStruct.new(the_hash)
p there_you_go.c #=> 20

instance_variable_set is intended for this kind of use case:
class A
def initialize(h)
h.each {|k,v| instance_variable_set("##{k}",v)}
end
end
It's a public method, so you could also call it after construction:
a = A.new({})
a.instance_variable_set(:#foo,1)
But note the implied warning in the documentation:
Sets the instance variable names by symbol to object, thereby frustrating the efforts of the class’s author to attempt to provide proper encapsulation. The variable did not have to exist prior to this call.

Related

Get attr_reader, writer, or accessor oustide of the class

I'm currently doing some metaprogramming with ruby, and I'm trying to isolate the methods of class (that class is in another file, that I get by a require). I can get all the methods, thanks to klass.public_instance_methods(false), but I in the sametime, the array given also have all the attributes of the class. How could I isolate them ? In others related questions on SO, they suggest to use klass.instance_variables but when I do that, it only returns an empty array.
I can't seem to wrap my head around that one. I don't understand why there isn't a method specifically for that already...
For example:
I have in a file this class :
class T
attr_reader:a
def initialize(a)
#a = a
end
def meth
#code here
end
end
And, in another file, i have
require_relative 'T.rb'
class meta
def initialize
methods = T.public_instance_methods(false) #=> here methods = [:a,:meth] but I would want only to have [:meth]
#rest of code
end
end
For class defined like this:
class Klass
attr_accessor :variable
def initialize(variable)
#variable = variable
end
def method
end
end
you can find public non-attr instance methods using public_instance_methods and instance_variables methods.
public_instance_methods = Klass.public_instance_methods(false)
# [:method, :variable, :variable=]
instance_variables = Klass.new(nil).instance_variables
# [:#variable]
getters_and_setters = instance_variables
.map(&:to_s)
.map{|v| v[1..-1] }
.flat_map {|v| [v, v + '=']}
.map(&:to_sym)
# [:variable, :variable=]
without_attr = public_instance_methods - getters_and_setters
# [:method]
This is impossible. Ruby's "attributes" are completely normal methods. There is no way to distinguish them from other methods. For example, these two classes are completely indistinguishable:
class Foo
attr_reader :bar
end
class Foo
def bar
#bar
end
end
You can try to be clever and filter them out based on instance variables, but that is dangerous:
class Foo
# can filter this out using #bar
attr_writer :bar
def initialize
#bar = []
end
end
class Foo
def initialize
#bar = []
end
# this looks the same as above, but isn't a normal attribute!
def bar= x
#bar = x.to_a
end
end

How can I add new attributes to a particular instance?

How can I add new attributes to a particular instance ?
For example here I want to add attr_acessors methods to attributes "m1","m2" in object e1 and "m4".."m6" to e2
e1=Element.new("e1",["m1","m2"])
e2=Element.new("e2",["m4","m5","m6"])
e1.m1 = "try"
puts e2.m4
To allow this :
class Element
attr_accessor :name
def initialize name,meths=[]
#name=name
meths.each do |m|
#??
end
end
end
Why not use a simple OpenStruct instead?
require 'ostruct'
e1 = OpenStruct.new
e1.m1 = 'try'
Alternatively, you can add attribute to any object using:
a.instance_eval('def m2; #m2; end; def m2=(x); #m2=x;end')
If you want to add attributes to all instances of specific class you can also:
a.class.instance_eval('attr_accessor :mmm')
Try this:
meths.each do |m|
singleton_class().class_eval do
attr_accessor m
end
end
where the singleton_class() method is defined as:
def singleton_class
class << self
self
end
end
(you probably want to make it private).
This will create the accessors only on the specific instance rather than on the Element class.
Here is a simpler solution:
methods.each do |method|
class << self
attr_accessor method
end
end
This way, you get rid of the extra method definition and class_eval because class << self already puts you into the scope of the eigenclass, where you add singleton methods.

Define methods from template at runtime in ruby

Say I have the following classes:
class Foo
attr_accessor :name, :age
end
class Bar
def initialize(name)
#foo = Foo.new
#foo.name = name
end
end
I'd like to define an accessor on Bar that is simply an alias to foo.name. Easy enough:
def name
#foo.name
end
def name=(value)
#foo.name = value
end
With only one property, this is easy enough. However, say Foo exposes several properties that I want to expose through Bar. Rather than defining each method manually, I want to do something like this, though I know the syntax isn't right:
[:name, :age].each do |method|
def method
#foo.method
end
def method=(value)
#foo.method = value
end
end
So...what is the correct way of defining methods like this?
To define a method dynamically you can use define_method which takes the method name as a symbol or string argument.
To call a method dynamically you can use send which also takes the method name as a symbol or string.
[:name, :age].each do |method|
define_method(method) do
#foo.send(method)
end
define_method("#{method}=") do |value|
#foo.send("#{method}=", value)
end
end
[:name, :age].each do |method|
define_method(method) do
foo.send(method)
end
define_method("#{method}=") do |value|
foo.send("#{method}=", value)
end
end
See the Delegator class in the Ruby Standard Library, depending on how many methods you want to pass along.

How can one set property values when initializing an object in Ruby?

Given the following class:
class Test
attr_accessor :name
end
When I create the object, I want to do the following:
t = Test.new {name = 'Some Test Object'}
At the moment, it results in the name attribute still being nil.
Is that possible without adding an initializer?
ok,
I came up with a solution. It uses the initialize method but on the other hand do exactly what you want.
class Test
attr_accessor :name
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('#' + key.to_s, val)
end
end
def display
puts #name
end
end
t = Test.new :name => 'hello'
t.display
happy ? :)
Alternative solution using inheritance. Note, with this solution, you don't need to explicitly declare the attr_accessor!
class CSharpStyle
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('#' + key.to_s, val)
instance_eval "class << self; attr_accessor :#{key.to_s}; end"
end
end
end
class Test < CSharpStyle
def initialize(arg1, arg2, *init)
super(init.last)
end
end
t = Test.new 'a val 1', 'a val 2', {:left => 'gauche', :right => 'droite'}
puts "#{t.left} <=> #{t.right}"
As mentioned by others, the easiest way to do this would be to define an initialize method. If you don't want to do that, you could make your class inherit from Struct.
class Test < Struct.new(:name)
end
So now:
>> t = Test.new("Some Test Object")
=> #<struct Test name="Some Test Object">
>> t.name
=> "Some Test Object"
There is a general way of doing complex object initialization by
passing a block with necessary actions. This block is evaluated in the
context of the object to be initialized, so you have an easy access to
all instance variables and methods.
Continuing your example, we can define this generic initializer:
class Test
attr_accessor :name
def initialize(&block)
instance_eval(&block)
end
end
and then pass it the appropriate code block:
t = Test.new { #name = 'name' }
or
t = Test.new do
self.name = 'name'
# Any other initialization code, if needed.
end
Note that this approach does not require adding much complexity
to the initialize method, per se.
As previously mentioned, the sensible way to do this is either with a Struct or by defining an Test#initialize method. This is exactly what structs and constructors are for. Using an options hash corresponding to attributes is the closest equivalent of your C# example, and it's a normal-looking Ruby convention:
t = Test.new({:name => "something"})
t = Test.new(name: "something") # json-style or kwargs
But in your example you are doing something that looks more like variable assignment using = so let's try using a block instead of a hash. (You're also using Name which would be a constant in Ruby, we'll change that.)
t = Test.new { #name = "something" }
Cool, now let's make that actually work:
class BlockInit
def self.new(&block)
super.tap { |obj| obj.instance_eval &block }
end
end
class Test < BlockInit
attr_accessor :name
end
t = Test.new { #name = "something" }
# => #<Test:0x007f90d38bacc0 #name="something">
t.name
# => "something"
We've created a class with a constructor that accepts a block argument, which is executed within the newly-instantiated object.
Because you said you wanted to avoid using initialize, I'm instead overriding new and calling super to get the default behavior from Object#new. Normally we would define initialize instead, this approach isn't recommended except in meeting the specific request in your question.
When we pass a block into a subclass of BlockInit we can do more than just set variable... we're essentially just injecting code into the initialize method (which we're avoiding writing). If you also wanted an initialize method that does other stuff (as you mentioned in comments) you could add it to Test and not even have to call super (since our changes aren't in BlockInit#initialize, rather BlockInit.new)
Hope that's a creative solution to a very specific and intriguing request.
The code you're indicating is passing parameters into the initialize function. You will most definitely have to either use initialize, or use a more boring syntax:
test = Test.new
test.name = 'Some test object'
Would need to subclass Test (here shown with own method and initializer) e.g.:
class Test
attr_accessor :name, :some_var
def initialize some_var
#some_var = some_var
end
def some_function
"#{some_var} calculation by #{name}"
end
end
class SubClassedTest < Test
def initialize some_var, attrbs
attrbs.each_pair do |k,v|
instance_variable_set('#' + k.to_s, v)
end
super(some_var)
end
end
tester = SubClassedTest.new "some", name: "james"
puts tester.some_function
outputs: some calculation by james
You could do this.
class Test
def not_called_initialize(but_act_like_one)
but_act_like_one.each_pair do |variable,value|
instance_variable_set('#' + variable.to_s, value)
class << self
self
end.class_eval do
attr_accessor variable
end
end
end
end
(t = Test.new).not_called_initialize :name => "Ashish", :age => 33
puts t.name #=> Ashish
puts t.age #=> 33
One advantage is that you don't even have to define your instance variables upfront using attr_accessor. You could pass all the instance variables you need through not_called_initialize method and let it create them besides defining the getters and setters.
If you don't want to override initialize then you'll have to move up the chain and override new. Here's an example:
class Foo
attr_accessor :bar, :baz
def self.new(*args, &block)
allocate.tap do |instance|
if args.last.is_a?(Hash)
args.last.each_pair do |k,v|
instance.send "#{k}=", v
end
else
instance.send :initialize, *args
end
end
end
def initialize(*args)
puts "initialize called with #{args}"
end
end
If the last thing you pass in is a Hash it will bypass initialize and call the setters immediately. If you pass anything else in it will call initialize with those arguments.

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