I'd like to replace the implementation of a method for an object with a block that the user specifies. In JavaScript, this is easily accomplished:
function Foo() {
this.bar = function(x) { console.log(x) }
}
foo = new Foo()
foo.bar("baz")
foo.bar = function(x) { console.error(x) }
foo.bar("baz")
In C# it is also quite easy
class Foo
{
public Action<string> Bar { get; set; }
public Foo()
{
Bar = x => Console.WriteLine(x);
}
}
var foo = Foo.new();
foo.Bar("baz");
foo.Bar = x => Console.Error.WriteLine(x);
foo.Bar("baz");
But how can I do the same in Ruby? I have a solution that stores a lambda in an instance variable and the method calls the lambda, but I don't really like the overhead and syntax
class Foo
def initialize
#bar = lambda {|x| puts x}
end
def bar x
#bar.call x
end
def bar= blk
#bar = blk
end
end
foo = Foo.new
foo.bar "baz"
foo.bar= lambda {|x| puts "*" + x.to_s}
foo.bar "baz"
I'd like to have a syntax like that:
foo.bar do |x|
puts "*" + x.to_s
end
foo.bar "baz"
I came up with the following code
class Foo
def bar x = nil, &blk
if (block_given?)
#bar = blk
elsif (#bar.nil?)
puts x
else
#bar.call x
end
end
end
But this is kinda ugly for more than one parameter and still doesn't feel 'right'. I could also define a set_bar method, but i don't like that either :).
class Foo
def bar x
if (#bar.nil?)
puts x
else
#bar.call x
end
end
def set_bar &blk
#bar = blk
end
end
So question is: Is there a better way do to do this and if not, what way would you prefer
Edit:
#welldan97's approach works, but i loose the local variable scope, i.e.
prefix = "*"
def foo.bar x
puts prefix + x.to_s
end
doesn't work. I suppose I have to stick with lambda for that to work?
use def:
foo = Foo.new
foo.bar "baz"
def foo.bar x
puts "*" + x.to_s
end
foo.bar "baz"
yes, that simple
Edit: To not loose the scope you can use define_singleton_method(as in #freemanoid answer):
prefix = "*"
foo.define_singleton_method(:bar) do |x|
puts prefix + x.to_s
end
foo.bar 'baz'
You can implement what you want like this:
class Foo; end
foo = Foo.new
prefix = '*'
foo.send(:define_singleton_method, :bar, proc { |x| puts prefix + x.to_s })
foo.bar('baz')
"*baz" <<<<<-- output
This is absolutely normal and correct in ruby.
Related
Ruby has support for autovivification for hashes by passing a block to Hash.new:
hash = Hash.new { |h, k| h[k] = 42 }
hash[:foo] += 1 # => 43
I'd like to implement autovivification for structs, also. This is the best I can come up with:
Foo = Struct.new(:bar) do
def bar
self[:bar] ||= 42
end
end
foo = Foo.new
foo.bar += 1 # => 43
and of course, this only autovivifies the named accessor (foo.bar), not the [] form (foo[:bar]). Is there a better way to implement autovivification for structs, in particular one that works robustly for both the foo.bar and foo[:bar] forms?
I would go with the following approach:
module StructVivificator
def self.prepended(base)
base.send(:define_method, :default_proc) do |&λ|
instance_variable_set(:#λ, λ)
end
end
def [](name)
super || #λ && #λ.() # or more sophisticated checks
end
end
Foo = Struct.new(:bar) do
prepend StructVivificator
end
foo = Foo.new
foo.default_proc { 42 } # declare a `default_proc` as in Hash
foo[:bar] += 1 # => 43
foo.bar += 1 # => 44
foo.bar above calls foo[:bar] under the hood through method_missing magic, so the only thing to overwrite is a Struct#[] method.
Prepending a module makes it more robust, per-instance and in general more flexible.
The code above is just an example. To copy the behavior of Hash#default_proc one might (credits to #Stefan for comments):
module StructVivificator
def self.prepended(base)
raise 'Sorry, structs only!' unless base < Struct
base.singleton_class.prepend(Module.new do
def new(*args, &λ) # override `new` to accept block
super(*args).tap { #λ = λ }
end
end)
base.send(:define_method, :default_proc=) { |λ| #λ = λ }
base.send(:define_method, :default_proc) { |&λ| λ ? #λ = λ : #λ }
# override accessors (additional advantage: performance/clarity)
base.members.each do |m|
base.send(:define_method, m) { self[m] }
base.send(:define_method, "#{m}=") { |value| self[m] = value }
end
end
def [](name)
super || default_proc && default_proc.(name) # or more sophisticated checks
end
end
Now default_proc lambda will receive a name to decide how to behave in such a case.
Foo = Struct.new(:bar, :baz) do
prepend StructVivificator
end
foo = Foo.new
foo.default_proc = ->(name) { name == :bar ? 42 : 0 }
puts foo.bar # => 42
puts foo[:bar] += 1 # => 43
puts foo.bar += 1 # => 44
puts foo[:baz] += 1 # => 1
A chained method should only be called under certain circumstances in the following code.
class Klass
def foo
puts 'foo'
self
end
def bar
puts 'bar'
self
end
end
klass = Klass.new
a = 2
id = klass.foo{conditionally chain bar if a == 2}.bar
Can you insert an expression or method between chained methods that conditionally continues or halts the method chain?
This is simple and who will come after you will understand immediately:
klass = klass.foo
klass = klass.bar if a == 2
etc...
This works well if the chained methods take no arguments
klass.define_singleton_method :chain_if do |b, *s|
return unless b
klass = self
s.each do |x|
klass = klass.send x
end
klass
end
klass.foo.chain_if(true, :foo, :bar).chain_if(false, :bar)
Here some duplicated threads!
conditional chaining in ruby
Add method to an instanced object
Here I found another solution that I personally like:
my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
EDIT:
beware tap is defined as follows:
class Object
def tap
yield self
self
end
end
What you need might look like this, if the chained method returns a new immutable object:
class Object
def tap_and_chain
yield self
end
end
def chain_maybe(klass, condition, *args)
args[1..-1].reduce(klass.send(args.first)) { |r,m| (condition && r.send(m)) || r }
end
or:
def chain_maybe(klass, condition, *args)
first, *others = args
others = [] unless condition
others.reduce(klass.send(first)) { |r,m| r.send(m) }
end
For:
class Klass
def foo
puts 'foo'
self
end
def bar
puts 'bar'
self
end
def baz
puts 'baz'
self
end
end
c = Klass.new
chain_maybe(c, true, :foo, :bar, :baz)
foo
bar
baz
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, false, :foo, :bar, :baz)
foo
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, true, :foo, :bar)
foo
bar
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, true, :bar, :baz)
bar
baz
#=> #<Klass:0x007fccea8da388>
If there is to be a condition for each argument, this can be generalized to:
def chain_maybe(klass, conditions, args)
return nil if conditions.empty?
first, *others = args.zip(conditions).select(&:last).map(&:first)
others.reduce(klass.send(first)) { |r,m| r.send(m) }
end
args = [:foo, :bar, :baz]
chain_maybe(c, [true, true, true], args)
foo
bar
baz
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, [false, true, true], args)
bar
baz
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, [true, false, true], args)
foo
baz
#=> #<Klass:0x007fccea8da388>
You can use tap if the block you are chaining is mutable, i.e: It will change the value of self (as explained here)
However, if you are chaining immutable blocks like QueryMethods, you can use try like:
data
.try { |d| group ? d.group(group) : d }
.try { |d| group ? d.order(order => :desc) : d }
try with nothing else but a block is essentially:
def try
yield self
end
(reference: #try)
I have the following kind of method definition:
method_name = :foo
method_arguments = [:bar, :baz]
method_mandatory_arguments = {:quux => true}
method_body = ->{ quux ? bar + baz : bar - baz }
So I want to get a real method. But define_method has no any possibility to define method arguments dynamically. I know another way to use class_eval but I know than defining methods with class_eval is much slower than define_method.
How I can effectively archive this?
I did some benchmarks in rails console:
class Foo; end
n = 100_000
Benchmark.bm do |x|
x.report('define_method') do
n.times { |i| Foo.send(:define_method, "method1#{i}", Proc.new { |a, b, c| a + b + c }) }
end
x.report('class_eval') do
n.times { |i| Foo.class_eval %Q{ def method2#{i}(a, b, c); a + b + c; end } }
end
end
So I've got the following results:
user system total real
define_method 0.750000 0.040000 0.790000 ( 0.782988)
class_eval 9.510000 0.070000 9.580000 ( 9.580577)
There is no simple way to implement what you are asking using either class_eval or define_method. Since method_body is a lambda it can only access local variables defined right before it.
method_body = ->{ quux ? bar + baz : bar - baz }
quux = true
method_body.call # undefined local variable or method ‘quux’
quux = true
method_body = ->{ quux ? bar + baz : bar - baz }
method_body.call # undefined local variable or method ‘bar’
I would suggest you to revise your requirements. If your method body should be a lambda, define all arguments for it. Then this is a natural fit for define_method:
method_body = ->(quux, bar = 0, baz = 0){ quux ? bar + baz : bar - baz }
define_method(method_name, &method_body)
If your method body can be a string, eval is the only option:
method_body = "quux ? bar + baz : bar - baz"
eval <<RUBY
def #{method_name} #{arguments}
#{method_body}
end
RUBY
class Foo
def initialize
#bar = []
end
def changed_callback
puts "Bar has been changed!"
end
def bar
#bar
end
def bar=(a)
#bar = a
self.changed_callback() # (hence why this doesn't just use attr_accessor)
end
def bar<<(a)
#bar.push(a)
self.changed_callback()
end
end
f = Foo.new()
f.bar = [1,2,3]
=> "Bar has been changed!"
f.bar << 4
=> "Bar has been changed!"
puts f.bar.inspect
=> [1,2,3,4]
Is anything like that possible?
Thanks!
You need to somehow extend the object returned by Foo#bar with an appropriate #<< method. Something like this, maybe?
class Foo
module ArrayProxy
def <<(other)
#__foo__.changed_callback
super
end
end
def initialize
#bar = []
end
def changed_callback
puts 'Bar has been changed!'
end
def bar
return #bar if #bar.is_a?(ArrayProxy)
#bar.tap {|bar| bar.extend(ArrayProxy).instance_variable_set(:#__foo__, self) }
end
def bar=(a)
#bar = a
changed_callback # (hence why this doesn't just use attr_accessor)
end
end
f = Foo.new
f.bar = [1,2,3]
# "Bar has been changed!"
f.bar << 4
# "Bar has been changed!"
puts f.bar.inspect
# => [1,2,3,4]
In Ruby, is there a way to redefine a method of a particular instance of a class using a proc? For example:
class Foo
def bar()
return "hello"
end
end
x = Foo.new
y = Foo.new
(Something like):
y.method(:bar) = lambda { return "goodbye" }
x.bar
y.bar
Producing:
hello
goodbye
Thanks.
def define_singleton_method_by_proc(obj, name, block)
metaclass = class << obj; self; end
metaclass.send(:define_method, name, block)
end
p = proc { "foobar!" }
define_singleton_method_by_proc(y, :bar, p)
or, if you want to monkey-patch Object to make it easy
class Object
# note that this method is already defined in Ruby 1.9
def define_singleton_method(name, callable = nil, &block)
block ||= callable
metaclass = class << self; self; end
metaclass.send(:define_method, name, block)
end
end
p = proc { "foobar!" }
y.define_singleton_method(:bar, p)
#or
y.define_singleton_method(:bar) do
"foobar!"
end
or, if you want to define your proc inline, this may be more readable
class << y
define_method(:bar, proc { "foobar!" })
end
or,
class << y
define_method(:bar) { "foobar!" }
end
this is the most readable, but probably doesn't fit your needs
def y.bar
"goodbye"
end
This question is highly related
I'm not sure what version of Ruby this was added in (at least 1.8.7), but there seems to be an even simpler way of doing this:
str1 = "Hello"
str2 = "Goodbye"
def str1.to_spanish
"Hola"
end
puts str1 # => Hello
puts str1.to_spanish # => Hola
puts str2 # => Goodbye
puts str2.to_spanish # => Throws a NoMethodError
Learnt about this whilst reading the Ruby Koans (about_class_methods.rb lesson).
I'm still not entirely sure what the purpose of this is since it seems a bit dangerous to me.
You can use the syntax class <<object to get an object's "singleton class" (that's a special parent class belonging only to that object) and define methods only for that instance. For example:
str1 = "Hello"
str2 = "Foo"
class <<str1
def to_spanish
'Hola'
end
end
Now if you do str1.to_spanish, it will return "Hola", but str2.to_spanish will give you a NoMethodFound exception.