Idiomatic object creation in ruby - ruby

In ruby, I often find myself writing the following:
class Foo
def initialize(bar, baz)
#bar = bar
#baz = baz
end
<< more stuff >>
end
or even
class Foo
attr_accessor :bar, :baz
def initialize(bar, baz)
#bar = bar
#baz = baz
end
<< more stuff >>
end
I'm always keen to minimise boilerplate as much as possible - so is there a more idiomatic way of creating objects in ruby?

One option is that you can inherit your class definition from Struct:
class Foo < Struct.new(:bar, :baz)
# << more stuff >>
end
f = Foo.new("bar value","baz value")
f.bar #=> "bar value"
f.baz #=> "baz value"

I asked a duplicate question, and suggested my own answer there, expecting for a better one, but a satisfactory one did not appear. I will post my own one.
Define a class method like the following along the spirit of attr_accessor, attr_reader, attr_writer methods.
class Class
def attr_constructor *vars
define_method("initialize") do |*vals|
vars.zip(vals){|var, val| instance_variable_set("##{var}", val)}
end
end
end
Then, you 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>

Struct
Struct object's are classes which do almost what you want. The only difference is, the initialize method has nil as default value for all it's arguments. You use it like this
A= Struct.new(:a, :b, :c)
or
class A < Struc.new(:a, :b, :c)
end
Struct has one big drawback. You can not inherit from another class.
Write your own attribute specifier
You could write your own method to specify attributes
def attributes(*attr)
self.class_eval do
attr.each { |a| attr_accessor a }
class_variable_set(:##attributes, attr)
def self.get_attributes
class_variable_get(:##attributes)
end
def initialize(*vars)
attr= self.class.get_attributes
raise ArgumentError unless vars.size == attr.size
attr.each_with_index { |a, ind| send(:"#{a}=", vars[ind]) }
super()
end
end
end
class A
end
class B < A
attributes :a, :b, :c
end
Now your class can inherit from other classes. The only drawback here is, you can not get the number of arguments for initialize. This is the same for Struct.
B.method(:initialize).arity # => -1

You could use Virtus, I don't think it's the idiomatic way to do so but it does all the boiler plate for you.
require 'Virtus'
class Foo
include 'Virtus'
attribute :bar, Object
attribute :baz, Object
end
Then you can do things like
foo = Foo.new(:bar => "bar")
foo.bar # => bar
If you don't like to pass an hash to the initializer then add :
def initialize(bar, baz)
super(:bar => bar, :baz => baz)
end
If you don't think it's DRY enough, you can also do
def initialize(*args)
super(self.class.attributes.map(&:name).zip(args)])
end

I sometimes do
#bar, #baz = bar, baz
Still boilerplate, but it only takes up one line.
I guess you could also do
["bar", "baz"].each do |variable_name|
instance_variable_set(:"##{variable_name}", eval(variable_name))
end
(I'm sure there's a less dangerous way to do that, mind you)
https://bugs.ruby-lang.org/issues/5825 is a proposal to make the boilerplate less verbose.

You could use an object as param.
class Foo
attr_accessor :param
def initialize(p)
#param = p
end
end
f = Foo.new
f.param.bar = 1
f.param.bax = 2
This does not save much lines in this case but it will if your class has to handle a large number of param. You could also implement a set_param and get_param method if you want to keep your #param var private.

Related

Instance variable array containing other instance variables doesn't reflect changes to contained elements

I noticed some weird behavior with instance variables in Ruby the other day. I was trying to add an instance variable array, containing other instance variable "attributes" of the class. The class is initialized without any parameters, but I still wanted to create this array at initialization. Here's an example of a (stripped-down) class:
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
end
end
Here's where it gets weird:
f = Foo.new #=><Foo.0x[object_id] #attrs=[nil, nil]>
f.bar = "bar" #=>"bar"
f.baz = "baz" #=>"baz"
f.attrs #=>[nil, nil]
At initialization, I can see that Foo.attrs is [nil, nil]. But after updating Foo.bar and Foo.baz, why is Foo.attrs still returning [nil, nil]? Why aren't their new values reflected?
I figured this wasn't the best way to do this, and found a way around it, but I'm still curious about this behavior.
Because that's how variables work, here and in virtually every other programming language.
Your array contains the values of #bar and #baz at the time the array was created. It does not contain references to the variables themselves. Modifying one does not modify the other.
Effectively you've done this:
x = 3;
y = x;
x = 4;
# Why doesn't y equal 4?
y is not 4 because x and y share a value but are otherwise unrelated. Reassigning x to a new value does not modify the value that y contains.
If you want this to work, you need to make an accessor that builds the array on-demand, using the current values of your member variables:
class Foo
attr_accessor :bar, :baz
def attrs
[#bar, #baz]
end
end
You can simply add a puts and see what happens
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
puts "inside initialize"
end
end
Now you can see initialize gets executed when you create an instance of Foo
f = Foo.new
#=> inside initialize
#=> #<Foo:0x2bc1bb0 #attrs=[nil, nil]>
f.bar = "bar" #=>"bar" , "inside initialize" not printed
If you do want to get them assigned then create a setter
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
puts "inside initialize"
end
def bar=(v)
#bar = v
#attrs = [#bar,#baz]
end
def baz=(v)
#baz = v
#attrs = [#bar,#baz]
end
end
f = Foo.new
#=> inside initialize
#=> #<Foo:0x2bc1bb0 #attrs=[nil, nil]>
f.bar = "bar"
f.attrs #=> ["bar", nil]
f.baz = "baz"
f.attrs #=> ["bar", "baz"]

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.

How to initialize class in block in Ruby?

I can't figure out the proper block initialize
class Foo
attr_accessor :bar
end
obj = Foo.new do |a|
a.bar = "baz"
end
puts obj.bar
Expect "baz"
instead get nil
What is the proper incantation for block class initializers in ruby?
Another way to make a block initializer would be writing it yourself one:
class Foo
attr_accessor :bar
def initialize
yield self if block_given?
end
end
And later use it:
foo = Foo.new do |f|
f.bar = true
end
My two cents.
Try again:
class Foo
attr_accessor :bar
end
obj = Foo.new.tap do |a|
a.bar = "baz"
end
puts obj.bar
I don't think new can take a block. Never saw it anywhere anyway. Why do you want to initialize in a block ? You can always do obj = foo.new.tap do |a| ... If you really want a block
actually you have a constructor for these purposes:
class Foo
attr_accessor :bar
def initialize(bar = "baz")
#bar = bar
end
end

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.

ruby how to define writer method with "<<"

I have a skeleton class:
class Foo
def bar
# returns some sort of array
end
end
but how can one add the 'writer' method to 'bar' so to enable the Array#push behavior?
Foo.new.bar<<['Smile']
_.bar #=> ['Smile']
EDITED:
I should expand my question further.
There are two classes. Foo, and Bar, much like the ActiveRecord has_many relation where Foo has_many Bars
But I am actually storing the ids of Bar inside a method of Foo. I name that method bar_ids
so #foo = Foo.new(:bar_ids => [1,2,3])
As you can imagine, if I ever want to look up what Bars belong to #foo, I have to actually do something like Bar.where(:id => #foo.bar_ids)
So I decided to make another method just named bar to do just that
class Foo
#...
def bar
Bar.where(:id => bar_ids)
end
end
That worked out. now I can do #foo.bar #=> all the bars belonging to #foo
Now I also want to have that kind of push method like ActiveRecord associations, just to cut out the "id" typing when associating another bar object to a foo object
Currently, this works:
#foo.bar_ids << Bar.new.id
#foo.save
But I want:
#foo.bar << Bar.new #where the new bar's id will get pushed in the bar_ids method of #foo
#foo.save
Thanks for all of your help, I really appreciate your thoughts on this!
class Foo
attr_reader :bar
def initialize
#bar = Array.new
def #bar.<< arg
self.push arg.id
end
end
end
class Bar
attr_accessor :id
def initialize id
self.id = id
end
end
f = Foo.new
bars = (1..5).map{|i| Bar.new i}
f.bar << bars[2]
f.bar << bars[4]
p f.bar #=> [3, 5]
Return an object that has the << method defined.
Unless I'm misunderstanding what you're wanting, why not just make the bar method a getter for an internal array member?
class Foo
attr_reader :bar
def initialize
#bar = []
end
end
f = Foo.new
f.bar << 'abc'
# f.bar => ['abc']

Resources