Ruby keyword arguments - ruby

I have the class. Method bar should accept argument foo with default value equal to #foo
class Foo
attr_accessor :foo
def bar(foo: foo)
p foo
end
end
In irb I execute:
> f = Foo.new
> f.foo = 'foobar'
> f.bar
For ruby 2.0 result is:
=> "foobar"
and for ruby 2.1:
=> nil
Who can explain this behavior?

Further research:
# (Ruby 2.1.0)
class Foo
attr_accessor :foo
def bar(foo: self.foo)
foo
end
end
f = Foo.new
f.foo = 'bar'
f.bar
# => "bar"
It seems Ruby 2.1.0 "initializes" local variable before evaluating the "right side" of this statement, so foo on the right side is treated as local variable and thus is evaluated to nil.
This experiment seems to confirm my hypothesis:
class Foo
attr_accessor :foo
def bar(foo: defined?(foo))
foo
end
end
# Ruby 2.0.0:
Foo.new.bar
# => "method"
# Ruby 2.1.0:
Foo.new.bar
# => "local-variable"

Related

Why does using class_eval cause toplevel class variable to be set?

I wrote some code to learn about the class_eval method. My understanding is that Foo.class_eval will set self to "Foo" and evaluate the block below in that context, just as saying "class Foo" will do. However, the code below shows that setting a class variable using class_eval will assign the class variable at the toplevel: to "Object". Why does this happen?
class Foo
end
class Foo
puts self
# => Foo
##class_var = "hello"
end
puts Object.class_variables
# => []
Foo.class_eval do
puts self
# => Foo
##class_var = "hi"
# => warning: class variable access from toplevel
end
puts Object.class_variables
# => ##class_var
As pointed out in comments a single # would do what you want, now if you were running the code from inside another class ## would work but it would set a class variable for both Foo, and whatever class you running from:
class Foo
puts self
# => Foo
##class_var = "hello"
end
class Bar
def self.main
puts Object.class_variables
# => []
Foo.class_eval do
puts self
# => Foo
##class_var = "hi"
end
end
end
Bar.main
p [Bar.class_variables]
#[[:##class_var]]
p [Foo.class_variables]
#[[:##class_var]]
That has to do with proc or block scope, I was expecting it to set only ##class_var only on Bar class but it actually sets on both, weird.

Setting a class instance variable of an extended class

How can Foo return the value of #baz. At the moment #baz returns nil.
module Bar
#baz = 'baz'
def get_baz
#baz
end
end
class Foo
extend Bar
end
Foo.get_baz # Currently returns nil
If this is not possible, is there a better way to implement this logic?
Try this:
module Bar
attr_accessor :baz
def get_baz
#baz
end
end
class Foo
extend Bar
end
Foo.baz = 'baz'
Foo.get_baz # => 'baz'
Foo.baz # => 'baz'
If we look at Foo's class methods, sure enough:
Foo.singleton_methods # => [:get_baz, :baz, :baz=]
Let's wander a bit (with module Bar unchanged):
class Foo
extend Bar
include Bar
end
foo = Foo.new
foo.methods.sort # => [:!,..., :baz, :baz=,..., :get_baz,...]
Foo.baz = 'baz'
foo.baz = 'cat'
Foo.baz # => "baz"
Foo.baz = 'dog'
foo.baz # => "cat"
goo = Foo.new
goo.baz = 'squid'
goo.baz # => 'squid'
We could instead use extend to bring in Bar's methods for just a particular instance of Foo:
class Foo
extend Bar
end
foo = Foo.new
foo.extend Bar
goo = Foo.new
We get the same results as above with Foo and foo, but
goo.baz = 'rhino' # => # NoMethodError: undefined method 'baz='
Can't keep extend and include straight? Maybe this will help: here extend Bar within the class definition is equivalent to Foo.singleton_class.send(:include, Bar).

Why can an instance method be called as a module method after include?

An instance method defined on a module:
module A
def foo; :bar end
end
seems to be able to be called as a module method of that module when that module is included:
include A
A.foo # => :bar
Why is that?
You're including A into Object.
module A
def self.included(base)
puts base.inspect #Object
end
def foo
:bar
end
end
include A
puts A.foo # :bar
puts 2.foo # :bar
#puts BasicObject.new.foo #this will fail
Also note that the top level object main is special; it's both an instance of Object and a sort of delegator to Object.
See http://banisterfiend.wordpress.com/2010/11/23/what-is-the-ruby-top-level/
Tried this out in irb, it included it in Object. include A also returns Object
irb > module A
irb > def foo; :bar end
irb > end
=> nil
irb > Object.methods.include? :foo
=> false
irb > include A
=> Object
irb > Object.methods.include? :foo
=> true

Aliasing a setter method

class Test
attr_accessor :something
end
class Test
alias :old_something= :something=
def something=(a)
a += 2 # do something with the argument
old_something=(a) # then pass it on
end
end
I would expect that if I said
t = Test.new
t.something = 3
puts t.something
It would print out 5. But it prints nil. Why does this not work?
The form
foo = bar
assigns to a local variable. You need to make it explicit that you want to call a method:
self.foo = bar
Edit: #Jörg's answer is probably the real problem, but my original answer below may be helpful as well. Redacted some misleading details from my old answer.
If you drop the def something= entirely, you'll get a setter for something which has an alias of old_something:
class Test
attr_accessor :something
end
class Test
alias :old_something= :something=
end
1.9.3p327 :001 > require "./test.rb"
=> true
1.9.3p327 :002 > t = Test.new
=> #<Test:0x000000018eb740>
1.9.3p327 :003 > t.something = "blah"
=> "blah"
1.9.3p327 :004 > t.something
=> "blah"
1.9.3p327 :005 > t.old_something = "moo"
=> "moo"
1.9.3p327 :006 > t.something
=> "moo"
The reason is that #something is never assigned to anything.
This code will do what you are trying to do:
class Test
attr_accessor :something
end
class Test
alias :old_something= :something=
def something=(a)
#something = a += 2 # do something with the argument
old_something=(a) # then pass it on
end
end
t = Test.new
t.something=( 3)
puts t.something
The difference was assinging the instance variable #something to a value, and then allowing your code to increment it by the a variable that was passed in.

Can I find out if an object is being referenced?

If I have two objects, one being referenced in another. Then in the first object can I write a method which will give me which other objects it is being referenced in?
I am not sure how to do it out of the box, but maybe the following post might help you:
What is a ruby object? (introducing Memprof.dump)
Perhaps digging around in ObjectSpace could help:
#!/usr/bin/ruby1.8
include ObjectSpace
def print_references_to_foos
for klass in [Bar, Baz]
each_object(klass) do |o|
s = o.inspect
puts s if s =~ /#<Foo/
end
end
end
class Foo
end
class Bar
def initialize(foo)
#foo = foo
end
end
class Baz < Bar
end
foo1 = Foo.new
foo2 = Foo.new
foo3 = Foo.new
bar1 = Bar.new(foo1)
bar2 = Bar.new(foo1)
bar3 = Baz.new(foo2)
print_references_to_foos
# => #<Baz:0xb7e09158 #foo=#<Foo:0xb7e091a8>>
# => #<Bar:0xb7e0916c #foo=#<Foo:0xb7e091d0>>
# => #<Bar:0xb7e09180 #foo=#<Foo:0xb7e091d0>>
# => #<Baz:0xb7e09158 #foo=#<Foo:0xb7e091a8>>

Resources