ruby how to define writer method with "<<" - ruby

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']

Related

Ruby: get a reference in a method context to the object that self is an attribute of

Suppose I have a setup like this:
class Foo
attr_accessor :bar
def initialize
#bar = Bar.new
end
end
class Bar
def bar_method
self.class # => Bar
whatever???.class # => Foo
end
end
foo = Foo.new
foo.bar.bar_method
I know that I can set up the method like this:
def bar_method(selfs_self)
selfs_self.class # => Foo
end
And call the method like this: foo.bar.bar_method(foo) to get what I want. But that seems pretty redundant. Is there any way, inside of bar_method, that I can get a reference to foo, without specifically passing in a reference to it?
No.
Usually this is done by passing a reference to the parent object when initializing child objects, like:
class Foo
attr_accessor :bar
def initialize
#bar = Bar.new(self)
end
end
class Bar
attr_reader :foo
def initialize(foo)
#foo = foo
end
def bar_method
self.class # => Bar
foo.class # => Foo
end
end

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"]

Variables in Ruby method names created by attr_accessor

I have an object that is using attr_accessor. I want to be able to call a method on that object with a variable with #send. My problem is that the = method doesn't seem to work.
class Foo
attr_accessor :bar
end
class Test_class
def test_method(x)
f = Foo.new
f.send(x)
f.send(x) = "test" #this doesnt work
f.send("#{x} =") "test" #this also doesn't work
# How can I set bar?
end
end
t = Test_class.new
t.test_method("bar")
You want f.send "#{x}=", "test". In Ruby, method names may include punctuation, such as = or !. The methods created by attr_accessor :bar are simply named bar and bar=. In fact, attr_accessor :bar is just shorthand for:
def bar
#bar
end
def bar=(value)
#bar = value
end
When you're calling foo.bar = "baz", you're actually calling the #bar= method with foo as the receiver and "bar" as the first parameter to the function - that is, foo.bar=("baz"). Ruby just provides syntactic sugar for methods ending in = so that you can write the more natural-looking form.

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

Idiomatic object creation in 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.

Resources