I would like to pass an argument(s) to a method being defined using define_method, how would I do that?
The block that you pass to define_method can include some parameters. That's how your defined method accepts arguments. When you define a method you're really just nicknaming the block and keeping a reference to it in the class. The parameters come with the block. So:
define_method(:say_hi) { |other| puts "Hi, " + other }
... and if you want optional parameters
class Bar
define_method(:foo) do |arg=nil|
arg
end
end
a = Bar.new
a.foo
#=> nil
a.foo 1
# => 1
... as many arguments as you want
class Bar
define_method(:foo) do |*arg|
arg
end
end
a = Bar.new
a.foo
#=> []
a.foo 1
# => [1]
a.foo 1, 2 , 'AAA'
# => [1, 2, 'AAA']
...combination of
class Bar
define_method(:foo) do |bubla,*arg|
p bubla
p arg
end
end
a = Bar.new
a.foo
#=> wrong number of arguments (0 for 1)
a.foo 1
# 1
# []
a.foo 1, 2 ,3 ,4
# 1
# [2,3,4]
... all of them
class Bar
define_method(:foo) do |variable1, variable2,*arg, &block|
p variable1
p variable2
p arg
p block.inspect
end
end
a = Bar.new
a.foo :one, 'two', :three, 4, 5 do
'six'
end
Update
Ruby 2.0 introduced double splat ** (two stars) which (I quote) does:
Ruby 2.0 introduced keyword arguments, and ** acts like *, but for keyword arguments. It returns a Hash with key / value pairs.
...and of course you can use it in define method too :)
class Bar
define_method(:foo) do |variable1, variable2,*arg,**options, &block|
p variable1
p variable2
p arg
p options
p block.inspect
end
end
a = Bar.new
a.foo :one, 'two', :three, 4, 5, ruby: 'is awesome', foo: :bar do
'six'
end
# :one
# "two"
# [:three, 4, 5]
# {:ruby=>"is awesome", :foo=>:bar}
Named attributes example:
class Bar
define_method(:foo) do |variable1, color: 'blue', **other_options, &block|
p variable1
p color
p other_options
p block.inspect
end
end
a = Bar.new
a.foo :one, color: 'red', ruby: 'is awesome', foo: :bar do
'six'
end
# :one
# "red"
# {:ruby=>"is awesome", :foo=>:bar}
I was trying to create example with keyword argument, splat and double splat all in one:
define_method(:foo) do |variable1, variable2,*arg, i_will_not: 'work', **options, &block|
# ...
or
define_method(:foo) do |variable1, variable2, i_will_not: 'work', *arg, **options, &block|
# ...
... but this will not work, it looks like there is a limitation. When you think about it makes sense as splat operator is "capturing all remaining arguments" and double splat is "capturing all remaining keyword arguments" therefore mixing them would break expected logic. (I don't have any reference to prove this point doh! )
update 2018 August:
Summary article: https://blog.eq8.eu/til/metaprogramming-ruby-examples.html
In addition to Kevin Conner's answer: block arguments do not support the same semantics as method arguments. You cannot define default arguments or block arguments.
This is only fixed in Ruby 1.9 with the new alternative "stabby lambda" syntax which supports full method argument semantics.
Example:
# Works
def meth(default = :foo, *splat, &block) puts 'Bar'; end
# Doesn't work
define_method :meth { |default = :foo, *splat, &block| puts 'Bar' }
# This works in Ruby 1.9 (modulo typos, I don't actually have it installed)
define_method :meth, ->(default = :foo, *splat, &block) { puts 'Bar' }
With 2.2 you can now use keyword arguments:
https://robots.thoughtbot.com/ruby-2-keyword-arguments
define_method(:method) do |refresh: false|
..........
end
Related
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 want to define a method that takes keyword arguments. I would like it to raise when keyword arguments are not provided, and I can code this myself - but ideally I would like to let Ruby do that for me. Also I would like to be able to inspect the freshly defined method using Method#parameters. If I use a shorthand double-splat (like **kwargs) the actual structure I expect is not visible to parameters.
I can of course do this:
define_method(:foo) do | foo:, bar: |
# ...
end
which achieves the desired result:
method(:foo).parameters
# => [[:keyreq, :foo], [:keyreq, :bar]]
but I cannot pass those arguments programmatically, they have to be literally placed in the code. Is there a way I could bypass this?
You have to use eval to define arguments dynamically (not just keyword arguments), e.g. using class_eval:
class MyClass
name = :foo
args = [:bar, :baz]
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{name}(#{args.map { |a| "#{a}:" }.join(', ')}) # def foo(bar:, baz:)
[#{args.join(', ')}] # [bar, baz]
end # end
METHOD
end
MyClass.new.foo(bar: 1, baz: 2)
#=> [1, 2]
MyClass.instance_method(:foo).parameters
#=> [[:keyreq, :bar], [:keyreq, :baz]]
def foo(bar)
'return value'
end
foo 'bar' # => "return value"
def foo=(bar)
'return value'
end
foo = 'bar' # => "bar"
send :foo=, 'bar' # => "return value"
I want foo = 'bar' to return "return value" but not to use send for this purpose. How can I do this?
Update
I need a desired behavior in my gem. Here is an example:
car = Car.new
car.gear # => :first
car.next_gear # => :second
car.gear # => :second
car.gear = :fourth # => false
car.gear # => :second
car.gear = :third # => :third
car.gear # => :third
Assignments always return the right hand side of an assignment.
Have a look at the ruby documentation for details:
Methods that end with an equals sign indicate an assignment method.
For assignment methods the return value is ignored, the arguments are
returned instead.
Having said that, foo = bar also assigns to a local variable foo instead of using the foo= method. Again, this is defined in the ruby docs:
When using method assignment you must always have a receiver. If you
do not have a receiver Ruby assumes you are assigning to a local
variable
You can test that by running
local_variables #=> []
def foo=(bar);end
foo = 42
local_variables #=> [:foo]
You see that the local variable foo was created. Better use self.foo = 'bar'.
To address your specific problem with your gem: Follow Neil's advice and use an extra method like change_gear for what you want to do. He gave you good council in his comments.
It's a Ruby gotcha: the return value of accessor methods get ignored.
This code will make it more clear what is actually happening:
#!/usr/bin/env ruby
def foo(bar)
p "called :foo w/ #{bar.inspect}"
end
def foo=(bar)
p "called :foo= with #{bar.inspect}"
end
ret = (foo :bar1) # calls foo(bar)
p "ret: #{ret}" # "ret: called :foo w/ :bar1"
ret = (foo = :bar2) # assigns a local variable foo = 'bar2'
p "ret: #{ret}" # "ret: bar2"
ret = (send :foo=, :bar3) # calls foo=(bar), returns what p returns
p "ret: #{ret}" # "ret: called :foo= with :bar3"
ret = (self.foo = :bar4) # calls foo=(bar), returns ???
p "ret: #{ret}" # "ret: bar4"
Basically, the Ruby parser (in 2.1 at least) behaves as if self.foo= was calling an accessor method (even if it actually isn't assigning anything), and will always return the value passed to it irrespective of what you sent it, rather than the accessor's return value.
Demonstration:
#!/usr/bin/env ruby
class << self
attr_accessor :foo
def foo=(bar)
p "called :foo= with #{bar.inspect}"
#foo = :baz
end
end
ret = (self.foo = :bar)
p "ret: #{ret} vs #foo: #{#foo.inspect}"
Outputs:
"called :foo= with :bar"
"ret: bar vs #foo: :baz"
Edit: hat #tessi for the reference:
Methods that end with an equals sign indicate an assignment method. For assignment methods the return value is ignored, the arguments are returned instead.
I think the reason why it's failing is that local variable names take precedence over method names when they are defined.
So you need to use send so that self knows it's looking for a method instead of a variable.
You need to do this:
self.foo = 'bar'
I'm puzzled over the following code:
Proc.new do |a|
a.something "test"
puts a.something
puts "hello"
end
It doesn't throw any errors when it runs. However nothing is printed for either puts statement. I'm curious about the a.something "assignment". Perhaps this is a method call w/ parens omitted. What is happening in the above code?
Proc.new ... # create a new proc
Proc.new{ |a| ... } # a new proc that takes a single param and names it "a"
Proc.new do |a| ... end # same thing, different syntax
Proc.new do |a|
a.something "test" # invoke "something" method on "a", passing a string
puts a.something # invoke the "something" method on "a" with no params
# and then output the result as a string (call to_s)
puts "hello" # output a string
end
Since the last expression in the proc is puts, which always returns nil, the return value of the proc if it is ever invoked will be nil.
irb(main):001:0> do_it = Proc.new{ |a| a.say_hi; 42 }
#=> #<Proc:0x2d756f0#(irb):1>
irb(main):002:0> class Person
irb(main):003:1> def say_hi
irb(main):004:2> puts "hi!"
irb(main):005:2> end
irb(main):006:1> end
irb(main):007:0> bob = Person.new
#=> #<Person:0x2c1c168>
irb(main):008:0> do_it.call(bob) # invoke the proc, passing in bob
hi!
#=> 42 # return value of the proc is 42
irb(main):009:0> do_it[bob] # alternative syntax for invocation
hi!
#=> 42
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