Say I have a class SomeClass with instance variables a and b.
def SomeClass
def initialize a, b
#a = a
#b = b
end
end
When I type into pry the following lines
someclass = SomeClass.new "one", "two"
someclass.instance_variables
As expected it prints an array with symbols
[:#a, :#b]
now, when I use
a.instance_variables.map do |var|
puts var
end
I expect it to print
:#a
:#b
but what I am getting is
#a
#b
Can someone please explain this behaviour ?
puts transparently calls to_s on arguments. Symbol#to_s returns symbol’s name (strings w/out preceding colon.) On the other hand, Array#to_s calls inspect on nested elements, that’s why you see colons while putting array’s instance.
Look:
▶ :a.to_s
#=> "a"
▶ [:a,:b,:c].to_s
#=> "[:a, :b, :c]"
This is exactly what you yield calling puts.
If you just want to print the symbols as
:#a
:#b
use 'p var', as p calls inspect on its argument unlike puts which calls to_s
Related
I have code:
class Blah
#hello = ["a", "b", "c"]
puts #hello[0]
def something()
puts "abc"
#puts #hello[0]
end
end
z = Blah.new()
z.something()
puts #hello[0]
It results in the output:
a
abc
If I uncomment
#puts #hello[0]
and try to output the first result of the array #hello, which is a, I get this error:
array_2.rb:13:in `something': undefined method `[]' for nil:NilClass (NoMethodError)
Why can I not get the result:
a
abc
a
Why doesn't my code work? An array such as #example should be accessible not just in the the class, but also in the something method. Why can't I access #hello[0] within methods? Why is #hello[0] only accessible within the class, and not the method? Need someone to fix my code so that I can access #array within the method.
You need to initialize instance variable in instance methods, but you are doing this in the scope of the class body, which isn't going to work.
If you set #hello from the initialize method, it should work like you expect.
class Blah
def initialize
#hello = ["a","b","c"]
end
def something()
puts #hello[0]
end
end
Blah.new.something #=> 'a'
It works this way so that you can pass arguments when instantiating a class, and each instance may have different data stored in its instance variables.
I'm trying to add an instance method foo to Ruby's Array class
so when it's invoked, the array's string elements are changed to string "foo".
This can be done easily by monkey patching Ruby's String and Array classes.
class String
def foo
replace "foo"
end
end
class Array
def foo
self.each {|x| x.foo if x.respond_to? :foo }
end
end
a = ['a', 1, 'b']
a.foo
puts a.join(", ") # you get 'foo, 1, foo' as expected
Now I'm trying to rewrite the above using Ruby 2's refinements feature.
I'm using Ruby version 2.2.2.
The following works (in a file, eg. ruby test.rb, but not in irb for some reason)
module M
refine String do
def foo
replace "foo"
end
end
end
using M
s = ''
s.foo
puts s # you get 'foo'
However, I can't get it to work when adding foo onto the Array class.
module M
refine String do
def foo
replace "foo"
end
end
end
using M
module N
refine Array do
def foo
self.each {|x| x.foo if x.respond_to? :foo }
end
end
end
using N
a = ['a', 1, 'b']
a.foo
puts a.join(", ") # you get 'a, 1, b', not 'foo, 1, foo' as expected
There're two issues:
After you refine a class with a new method, respond_to? does not work even when you can invoke
the method on an object. Try adding puts 'yes' if s.respond_to? :foo
as the last line in the second code snippet, you'll see 'yes' is not printed.
In my Array refinement, the String#foo is out of scope. If you remove if x.respond_to? :foo from
the Array#foo, you'll get the error undefined method 'foo' for "a":String (NoMethodError). So the question is: how do you make the String#foo refinement visible inside the Array#foo refinement?
How do I overcome these two issues so I can get this to work?
(Please don't offer alternative solutions that don't involve refinement, because this is a theoretical exercise so I can learn how to use refinement).
Thank you.
The respond_to? method does not work and this is documented
here.
The problem is that you can only activate a refinement at top-level
and they are lexical in scope.
One solution would be:
module N
refine String do
def foo
replace 'foobar'
end
end
refine Array do
def foo
self.each do |x|
x.foo rescue x
end
end
end
end
using N
a = ['a', 1, 'b']
p a.foo
puts a.join(", ") # foo, 1, foo
Taking up your example again, a simple solution could be to override the respond_to? method in refinement block :
module M
refine String do
def foo
replace "foo"
end
def respond_to?(name,all=false)
list_methods = self.methods.concat [:foo]
list_methods.include? name
end
end
refine Array do
def foo
self.each {|x| x.foo if x.respond_to? :foo }
end
end
end
using M
a = ['a', 1, 'b']
a.foo
puts a.join(", ") # you get 'foo, 1, foo'
i don't understand.
this one works:
class Foo
def initialize(*args)
#variables = *args unless args[0].eql?(nil)
end
end
this one don't:
class Foo
def initialize(*args)
args[0].eql?(nil) ? #multiples = [3,5] : #multiples = *args
end
end
but this one works too:
class Foo
def initialize(*args)
args[0].eql?(nil) ? #multiples = [3,5] : #multiples = args
end
end
my question is about 'args' and '*args' between the second form and the third.
why is that ?
The splat operator (the star *), as you know, when used in method definitions, will capture an unlimited number of arguments and convert them to an array. However, its purpose IS DIFFERENT when used in a METHOD body. There's no point of putting the splat * in a method body when doing an assignment because
a) You'll get the same result as if you didn't put it.
class Foo
def initialize(*args)
#a = args
#b = *args
p #a == #b
end
end
Foo.new(1,2,3) #=> prints 'true'
b) You'll get strange errors like in your second example which probably produces an error because Ruby confuses the precedence of the operators. It will work if you put parentheses around the whole expression, however:
class Foo
def initialize(*args)
args[0].eql?(nil) ? #multiples = [3,5] : (#multiples = *args)
end
end
So to sum up, when doing an assignment of a splat to a variable, don't use the * .
Btw, a bit off topic, instead of doing args[0].eql?(nil), you can just do arg[0].nil? Also, instead of assigning #multiples twice, you can do something like:
#multiples = args[0].nil? ? [3,5] : args
I use blocks to create values like so
some_block = BlockClass.new {|b|
b.one = 1
b.two = 2
b.three = 3
}
Here is BlockClass
class BlockClass
attr_accessor :one
attr_accessor :two
attr_accessor :three
def initialize
yield self if block_given?
end
end
I need a way to iterate over some_block, and print all the value in the block without having to do
puts some_block.one
puts some_block.two
puts some_block.three
Is there a way to iterate over the values in the block?
First of all, the b parameter in the block is nil, so you will get a
NoMethodError: undefined method `one=' for nil:NilClass`
To fix this, you can change yield if block_given? to yield(self) if block_given?, which will pass self as the first parameter to the block.
If you want the b.one = ..., b.two = ... syntax, you should use an OpenStruct:
require 'ostruct'
class BlockClass < OpenStruct
def initialize
super
yield(self) if block_given?
end
end
You can get a dump of the internal Hash by calling marshal_dump:
some_block = BlockClass.new {|b|
b.one = 1
b.two = 2
b.three = 3
}
some_block.marshal_dump # => {:one=>1, :two=>2, :three=>3}
You can then iterate over the values:
some_block.marshal_dump.each_pair do |k, v|
puts "the value of #{k} is #{v}"
end
Your block takes 1 parameter, b, but your yield statement doesn't pass anything in. Perhaps you mean, yield self if block_given??
Also, if you want to "iterate", you'll need an enumerable collection of something, like an Array or Hash. As is, one, two, and three are totally unrelated accessors to your BlockClass.
You could iterate over all methods of BlockClass:
(some_block.methods).each do |method_name|
puts some_block.send(method_name)
end
But that doesn't sound like what you're looking for. Perhaps Initialize a Ruby class from an arbitrary hash, but only keys with matching accessors might help?
As we know, wo can pass a method to a iterator method by a &: prefix.
For example:
["a", "b"].map(&:upcase) #=> ["A", "B"]
def rettwo
2
end
["a", "b"].map(&:rettwo) #=> [2, 2]
Here is the problem, when I write a method, pass a method with &: prefix to it, I got a error message: "ArgumentError: no receiver given".
Let me show the code:
def a_simple_method &proc
puts proc.class # it shows `Proc`
proc.call
end
def a_iterator_method
puts yield
end
a_simple_method &:rettwo #=> ArgumentError: no receiver given
a_iterator_method &:rettwo #=> ArgumentError: no receiver given
What do I missing, How the map like method of Array handle it
Here's what works. Explanation below.
class String
def rettwo
self + self
end
end
def a_simple_method &proc
proc.call('a')
end
def a_iterator_method
yield 'b'
end
a_simple_method(&:rettwo) # => "aa"
a_iterator_method(&:rettwo) # => "bb"
The &: construct is called Symbol#to_proc. It turns symbol into a proc. This proc expects a receiver as a first argument. The remaining arguments are used to call the proc. You're not passing any arguments, hence the "receiver not given" error.
Here's a demonstration of additional arguments:
class String
def say name
"#{self} #{name}"
end
end
def a_simple_method &proc
proc.call('hello', 'ruby')
end
a_simple_method(&:say) # => "hello ruby"
Here's a definition of Symbol#to_proc from some blog post from 2008. Modern Symbol#to_proc seems to be implemented in C, but this can still help the understanding.
class Symbol
def to_proc
Proc.new { |*args| args.shift.__send__(self, *args) }
end
end