Why can you chain this:
"Test".upcase.reverse.next.swapcase
but not this:
x = My_Class.new
x.a.b.c
where
class My_Class
def a
#b = 1
end
def b
#b = #b + 2
end
def c
#b = #b -72
end
end
The upcase, reverse, next and swapcase methods all return String objects and all those methods are for... you guessed it, String objects!
When you call a method (more often than not, like 99.9999% of the time) it returns an object. This object has methods defined on it which can then be called which explains why you can do this:
"Test".upcase.reverse.next.swapcase
You can even call reverse as many times as you like:
"Test".reverse.reverse.reverse.reverse.reverse.reverse.reverse.reverse
All because it returns the same kind of object, a String object!
But you can't do this with your MyClass:
x = My_Class.new
x.a.b.c
For that to work, the a method would have to return an object which has the b method defined on it. Right now, that seems like only instances of MyClass would have that. To get this to work you could make the return value of a the object itself, like this:
def a
#b += 2
self
end
Extrapolating this, the b method would also need to return self as the c method is available only on instances of the MyClass class. It's not important what c returns in this example, because it's the end of the chain. It could return self, it could not. Schrödinger's cat method. Nobody knows until we open the box.
As support for other answers, this code:
"Test".upcase.reverse.next.swapcase
...is almost exactly the same as...
a = "Test"
b = a.upcase
c = b.reverse
d = c.next
e = d.swapcase
....except that my code above has extra variables left over pointing to the intermediary results, whereas the original leaves no extra references around. If we do this with your code:
x = MyClass.new # x is an instance of MyClass
y = x.a # y is 1, the last expression in the a method
z = y.b # Error: Fixnums have no method named 'b'
Using Ruby 1.9's tap method, we can even make this more explicit:
irb> "Test".upcase.tap{|o| p o}.reverse.tap{|o| p o}.next.tap{|o| p o}.swapcase
#=> "TEST"
#=> "TSET"
#=> "TSEU"
=> "tseu"
irb> class MyClass
irb> def a
irb> #b = 1
irb> end
irb> def b
irb> #b += 2
irb> end
irb> end
=> nil
irb(main):011:0> x = MyClass.new
=> #<MyClass:0x000001010202e0>
irb> x.a.tap{|o| p o}.b.tap{|o| p o}.c
#=> 1
NoMethodError: undefined method `b' for 1:Fixnum
from (irb):12
from /usr/local/bin/irb:12:in `<main>'
The last expression in a function is its implicit return value. You need to return self if you want to chain methods like that.
For example, your a method is currently returning 1. b is not a method for numbers. You'll want to modify it like so:
def a
#b = 1
self
end
Related
I have a class Foo with a large number of instance methods
class Foo
def initalize(ary)
#q = ary
end
def w
q[0]
end
def x
q[1]
end
...
private
attr_reader :q
end
This class is used to implement mathematical algorithms involving operations on the qw, qx, etc.
I could implement the algorithms as follows
foo = Foo.new([1,2])
s = foo.w * foo.x + foo.x * foo.x
but some of the algorithms can require many lines and hundreds of terms like this, so repeating the foo. is tedious and gets in the way of readability. Is there a way in ruby so you can do something like this
foo = Foo.new
using foo do
s = w * x + x * x
end
Is there a way in ruby so you can do something like this...
foo = Foo.new
using foo do
s = w * x + x * x
end
Yes ruby does provide a method, BasicObject#instance_eval, that allows you to execute a block of code in the context of a given instance of an object. (using is a method that activates a Refinement which is a whole different discussion)
You can utilize instance_eval like so to achieve your goal:
class Foo
def w
1
end
def x
2
end
end
foo = Foo.new
foo.instance_eval do
w * x + x * x
end
#=> 6
instance_eval does pass the receiver to the block however inside the block the implicit self is the receiver, allowing you to achieve your desired syntax.
There is one distinct difference to recognize between the implicit and explicit object within instance_eval which is that the one can implicitly call private methods however private methods cannot be called with an explicit receiver (other than self) so the following occurs
class Foo
def bar
# self is not required just showing it is a valid receiver for the baz message
self.baz
end
private
def baz
'private'
end
end
foo = Foo.new
foo.instance_eval {|f| f == self} #=> true
foo.instance_eval {|f| f.bar} #=> 'private'
foo.instance_eval {|f| f.baz} #=> NoMethodError (private method `baz' called...
foo.instance_eval {|f| baz} #=> 'private'
class Test
def method
a = 2
b = 3
yield
c = 4
a *= c
a
end
def method_x
method {}
end
def method_y
method { a += b }
end
end
test = Test.new
p test.method_x
p test.method_y
This above code doesn't work well.
$ ruby code.rb
8
code.rb:16:in `block in method_y': undefined local variable or method `b' for #<Test:0x007fe6da094890> (NameError)
from code.rb:5:in `method'
from code.rb:16:in `method_y'
from code.rb:22:in `<main>'
I had expected that test.method_y returns 20.
I'd like to insert reflection code within a specific process.
But I don't know to use local variables on destination code block within reflection code.
Could you tell me how to use local variables or a good design?
The block doesn't have access to the method variables. You need to pass those variables into the block.
class Test
def method
a = 2
b = 3
a = yield(a,b) || a
c = 4
a *= c
a
end
def method_x
method {}
end
def method_y
method { |x, y| x += y }
end
end
test = Test.new
p test.method_x
p test.method_y
Since the block might return nil, the above code only moves the result of the block into a if it's not nil, otherwise it leaves a unchanged.
EDIT
It's also possible that you don't pass a block into a method... the block can be optional.
In which case you might want to do...
(a = yield(a,b) || a) if block_given?
Following is the way, you can pass variables in the ruby code block:
class Test
def method
a = 2
b = 3
a = block_given? ? a : yield(a, b)
c = 4
a *= c
a
end
def method_x
method {}
end
def method_y
method {|a,b| a += b }
end
end
test = Test.new
p test.method_x
p test.method_y
The problem with your code is it doesn't modifies your local variable and the syntax is used wrong to pass variable in the code block. One useful link on how to use code block in ruby is here.
How can I call a nested hash of methods names on an object?
For example, given the following hash:
hash = {:a => {:b => {:c => :d}}}
I would like to create a method that, given the above hash, does the equivalent of the following:
object.send(:a).send(:b).send(:c).send(:d)
The idea is that I need to get a specific attribute from an unknown association (unknown to this method, but known to the programmer).
I would like to be able to specify a method chain to retrieve that attribute in the form of a nested hash. For example:
hash = {:manufacturer => {:addresses => {:first => :postal_code}}}
car.execute_method_hash(hash)
=> 90210
I'd use an array instead of a hash, because a hash allows inconsistencies (what if there is more than one key in a (sub)hash?).
object = Thing.new
object.call_methods [:a, :b, :c, :d]
Using an array, the following works:
# This is just a dummy class to allow introspection into what's happening
# Every method call returns self and puts the methods name.
class Thing
def method_missing(m, *args, &block)
puts m
self
end
end
# extend Object to introduce the call_methods method
class Object
def call_methods(methods)
methods.inject(self) do |obj, method|
obj.send method
end
end
end
Within call_methods we use inject in the array of symbols, so that we send every symbol to the result of the method execution that was returned by the previous method send. The result of the last send is automatically returned by inject.
There's a much simpler way.
class Object
def your_method
attributes = %w(thingy another.sub_thingy such.attribute.many.method.wow)
object = Object.find(...)
all_the_things << attributes.map{ |attr| object.send_chain(attr.split('.')) }
end
def send_chain(methods)
methods.inject(self, :try)
end
end
There is no predefined method, but you can define your own method for that:
class Object
def send_chain(chain)
k = chain.keys.first
v = chain.fetch(k)
r = send(k)
if v.kind_of?(Hash)
r.send_chain(v)
else
r.send(v)
end
end
end
class A
def a
B.new
end
end
class B
def b
C.new
end
end
class C
def c
D.new
end
end
class D
def d
12345
end
end
chain = { a: { b: { c: :d } } }
a = A.new
puts a.send_chain(chain) # 12345
Tested with http://ideone.com/mQpQmp
So, I'd like to be able to make a call
x = MyClass.new('good morning', 'good afternoon', 'good evening', 'good night',
['hello', 'goodbye'])
that would add methods to the class whose values are the values of the arguments. So now:
p x.methods #> [m_greeting, a_greeting, e_greeting, n_greeting,
r_greeting, ...]
And
p x.m_greeting #> "good morning"
p x.r_greeting #> ['hello', 'goodbye']
I realize that this is sort of what instance variables are to do (and that if I wanted them immutable I could make them frozen constants) but, for reasons beyond my control, I need to make methods instead.
Thanks!
BTW: I tried
def initialize(*args)
i = 0
%w[m_a, m_b, m_c].each do |a|
self.class.send(:define_method, a.to_s, Proc.new { args[i] })
i+=1
end
end
But that ended up giving every method the value of the last argument.
I guess this solves the problem:
def initialize(*args)
#args = args
%w[m_a m_b m_c].each_with_index do |a, i|
eval "def #{a}; #args[#{i}]; end"
end
end
You can do what you want, like so:
class Foo
def initialize(*args)
methods = %w[m_greeting a_greeting e_greeting n_greeting r_greeting]
raise ArgumentError unless args.size == methods.size
args.zip(methods).each do |arg, method|
self.class.instance_eval do
define_method method do
arg
end
end
end
end
end
foo = Foo.new(1, 2, 3, 4, 5)
p foo.m_greeting # => 1
p foo.a_greeting # => 2
p foo.e_greeting # => 3
p foo.n_greeting # => 4
p foo.r_greeting # => 5
But this may not be the droid you're looking for: More than a few positional arguments can make code difficult to read. You might consider using OpenStruct. You'll have to write almost no code, and the constructor calls will be easier to read:
require 'ostruct'
class Foo < OpenStruct
end
foo = Foo.new(:m_greeting=>1,
:a_greeting=>2,
:e_greeting=>3,
:n_greeting=>4,
:r_greeting=>5)
p foo.m_greeting # => 1
p foo.a_greeting # => 2
p foo.e_greeting # => 3
p foo.n_greeting # => 4
p foo.r_greeting # => 5
Don't sweat mutability. If you feel the need to write code to protect yourself from mistakes, consider writing unit tests instead. Then the code can be unfettered with sundry checks and protections.
Your last loop would send the last argument to redefine the method for each of your m_a, m_b, m_c Try looping over the args and sending to the indexed method.
e.g.
def initialize(*args)
methods = %w[m_a m_b m_c]
args.each_with_index {|item,index|
self.class.send(:define_method, methods[index], lambda { item })
}
end
each_with_index comes from the Enumerable module: http://ruby-doc.org/core/classes/Enumerable.html#M003137
Assume, I have an object x of MyClass. What method is called, when I do puts x? I need to override it with my own one.
I thought it was .inspect, but somehow overridden inspect isn't being called.
For example, I have a class Sum:
class Sum
initiazlie a, b
#x = a + b
end
end
And I wanna access the result like this:
s = Sum.new(3,4)
puts s #=> 7 How do I do this?
puts 10 + s #=> 17 or even this...?
It calls: to_s
class Sum
def to_s
"foobar"
end
end
puts Sum.new #=> 'foobar'
Or if you want, you can just call inspect from to_s, so that you have a consistent string representation of your object.
class Sum
def to_s
inspect
end
end
First, your Sum class is invalid. The definition should be.
class Sum
def initialize(a, b)
#x = a + b
end
end
By default, the method called to get a human readable representation is inspect. Try this in irb
$ s = Sum.new(3, 4)
# => #<Sum:0x10041e7a8 #x=7>
$ s.inspect
# => "#<Sum:0x10041e7a8 #x=7>"
But in your case, you use the puts method which forces a string conversion. For this reason, the Sum object is first converted to string using the to_s method.
$ s = Sum.new(3, 4)
# => #<Sum:0x10041e7a8 #x=7>
$ puts s
# => #<Sum:0x10041e7a8>
$ puts s.to_s
# => #<Sum:0x10041e7a8>
Also note your last example fall into a third case. Because you sum a Fixnum + an other object, the result is expected to be a Fixnum and the method called is to_s but the one defined in the Fixnum class.
In order to use the one in your Sum class, you need to switch the items in the sum and define the + in your object.
class Sum
def initialize(a, b)
#x = a + b
end
def +(other)
#x + other
end
def to_s
#x.to_s
end
end
s = Sum.new(3, 4)
s + 10
puts s
# => 17