Having this method which can dynamically send args to an object:
module DSL
def update object, *args, &block
updated_object = object.send *args
# then, some stuff with the updated object and the block
end
end
the following code could be used such as:
include DSL
# equivalent to: Array.new 3, :foo
update Array, :new, 3, :foo do
# updated object => [:foo, :foo, :foo]
# some stuff
end
or such as:
# equivalent to: [:foo, :foo, :foo].size
update [:foo, :foo, :foo], :size do
# updated object => 3
# some stuff
end
But how could we update the content of this update method in order to handle blocks, like here:
[:foo, :bar].select {|a| a == :foo }
I thought about converting the block into a proc, such as:
update [:foo, :bar], :select, &:foo.method(:==) do
# ...
end
But then, because a same method can not handle more than one block, this exception is raised:
SyntaxError: both block arg and actual block given
Is there an elegant way to solve this?
I think that you're going over the top with this, but anyway, here's an idea:
You could pass a regular proc to your update method and then have special handling for last argument, which is a proc.
module DSL
def update object, *args, &block
updated_object = if args.last.is_a? Proc
proc = args.pop
object.send *args, &proc
else
object.send *args
end
yield updated_object
end
end
extend DSL
update [:foo, :bar], :select, ->(a) { a == :foo } do |obj|
puts "selected object: #{obj}"
end
# >> selected object: [:foo]
Related
I am trying to implement a class that takes a hash as a constructor as part of my Ruby learning.
This is what I have so far:
class Example
attr_accessor :bar, :baz, :some
def initialize args
args.each do |k,v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
my_hash = Example.new(foo: {bar: "x", "baz" => "y"}, "some" => "other")
end
The above is supposed to read the values as follows:
my_hash.foo.bar
=> "x"
my_hash.foo.baz
=> "y"
my_hash.some
=> "other"
my_hash.foo.class
=> Example
my_hash.unknown
=> nil
and also override the values and assign new ones. However, after I implemented this apparently, this code snippet here:
def initialize args
args.each do |k,v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
is no good because it does not allow assigning of params? Not sure what that means.
This is one of the tests I am not passing
it 'should store my_hash from hash passed through constructor' do
my_hash = Example.new({p1: 'test', 'p2' => {'p3' => [10, 12]}, p4: {}})
expect(my_hash.p1).to eq('test')
expect(my_hash.p2.p3).to eq([10, 12])
expect(my_hash.p2).to be_a(Example)
end
And the test that is failing my initialize method is this one:
it 'should allow assigning config params' do
my_hash = Example.new
my_hash.some = 'test'
my_hash.other = 'test2'
my_hash.yet_one_more = {}
my_hash.yet_one_more.test = 'test'
expect(my_hash.some).to eq('test')
expect(my_hash.other).to eq('test2')
expect(my_hash.yet_one_more.test).to eq('test')
end
In your example you are passing a hash to the initialize method and then iterating a new instance variable for each key-value pair. Considering this, the only instance variables you are creating are:
#foo = {bar: "x", "baz" => "y"}
#some = 'other'
Therefore you only need attribute accessors for said variables.
# this allows you to access to whatever `foo` and `some` are holding,
# including nested properties.
attr_accessor :foo, :some
Oh, and you're getting an error because you're trying to create a variable my_hash inside the class (you're trying to instance a class inside its own definition). This is how you would define it:
class Example
attr_accessor :foo, :some
def initialize args
args.each do |k, v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
end
my_hash = Example.new(foo: {bar: "x", "baz" => "y"}, "some" => "other")
# Your #foo instance variable which you can access and modify
puts my_hash.foo
# your #foo's bar property which was defined as symbol
puts my_hash.foo[:bar]
# your #foo's baz property which was defined as string
puts my_hash.foo["baz"]
# your #some instance variable
puts my_hash.some
# Notice that even though `foo` was defined as symbol and `some` as
# string, the accessor allows you to get their values by using the
# dot notation.
Edit: From the tests you added I'm guessing that they expect you to create a new instance if the parameter passed is a nested hash. If so, you could do this:
class Example
def initialize args
args.each do |k, v|
# Create a new Example instance if the value is a hash
v = self.class.new(v) if v.class == Hash
instance_variable_set("##{k}", v) unless v.nil?
# Dynamically set the accessor for each attribute.
self.class.__send__(:attr_accessor, k)
end
end
end
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]]
While playing a bit with Ruby, I wrote the following code:
class A
end
A.singleton_class.instance_eval do
undef_method :new
end
# or
# class << B
# undef_method :new
# end
A.new
> NoMethodError: undefined method `new' for A:Class
> from (irb):8
> from /home/mmsequeira/.rvm/rubies/ruby-1.9.3-p327/bin/irb:16:in `<main>'
This is cool. But how can I know which methods have been undefined in a given class?
You can't by default. Undefining a method removes it from existence. You could, however, record them as they're removed. This can be done with method hooks to capture everything and avoid ugly alias method chaining:
class Module
def method_undefined name
(#undefined_methods ||= []) << name
end
def singleton_method_undefined name
(#undefined_methods ||= []) << name
end
def undefined_methods
#undefined_methods || []
end
end
This will capture methods undefined via undef_method or undef:
class C
def foo; end
def bar; end
undef foo
undef_method :bar
end
C.undefined_methods #=> [:foo, :bar]
C.singleton_class.instance_eval { undef new }
C.singleton_class.undefined_methods #=> [:new]
Of course, you must include the hook methods in Module before anything can be captured.
Maybe you need to redefine Module#undef_method.
class Module
alias original_undef_method :undef_method
##undef_methods = {}
def undef_method *methods
methods.each{|method| ##undef_methods[[self, method]] ||= true}
original_undef_method(*methods)
end
def self.undef_methods; ##undef_methods.keys end
end
Then, you get:
class A
end
A.singleton_class.instance_eval do
undef_method :new
end
Module.undef_methods
# => [[#<Class:A>, :new]]
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Idiomatic object creation in ruby
There are many occaisions when I have an initialize method that looks like this:
class Foo
def initialize bar, buz, ...
#bar, #buz, ... = bar, buz, ...
end
end
Is there a way to do this with a simple command like:
class Foo
attr_constructor :bar, :buz, ...
end
where the symbols represent the name of the instance variables (with the spirit/flavor of attr_accessor, attr_reader, attr_writer)?
I was wondering if there is a built in way or a more elegant way of doing something like this:
class Class
def attr_constructor *vars
define_method("initialize") do |*vals|
vars.zip(vals){|var, val| instance_variable_set("##{var}", val)}
end
end
end
so that I can use it like this:
class Foo
attr_constructor :foo, :bar, :buz
end
p Foo.new('a', 'b', 'c') # => #<Foo:0x93f3e4c #foo="a", #bar="b", #buz="c">
p Foo.new('a', 'b', 'c', 'd') # => #<Foo:0x93f3e4d #foo="a", #bar="b", #buz="c">
p Foo.new('a', 'b') # => #<Foo:0x93f3e4e #foo="a", #bar="b", #buz=nil>
I'd use OpenStruct:
require 'ostruct'
class Foo < OpenStruct
end
f = Foo.new(:bar => "baz")
f.bar
#=> "baz"
Edit: Ah OK, sorry misunderstood you. How about just:
class Foo
def initialize(*args)
#baz, #buz = args
end
end
Would this work for you?
class Foo
def initialize(hash)
hash.each { |k,v| instance_variable_set("##{k}", v) }
end
end
Interesting question. A little meta-programming should take care of it.
module Attrs
def self.included(base)
base.extend ClassMethods
base.class_eval do
class << self
attr_accessor :attrs
end
end
end
module ClassMethods
# Define the attributes that each instance of the class should have
def has_attrs(*attrs)
self.attrs = attrs
attr_accessor *attrs
end
end
def initialize(*args)
raise ArgumentError, "You passed too many arguments!" if args.size > self.class.attrs.size
# Loop through each arg, assigning it to the appropriate attribute (based on the order)
args.each_with_index do |val, i|
attr = self.class.attrs[i]
instance_variable_set "##{attr}", val
end
end
end
class Foo
include Attrs
has_attrs :bar, :buz
end
f = Foo.new('One', 'Two')
puts f.bar
puts f.buz
Of course the downside to this is inflexibility - you have to pass your constructor arguments in a specific order. Of course that's how most programming languages are. Rails people might argue you should instead do
f = Foo.new(:bar => 'One', :baz => 'Two')
which would allow you to pass in attrs in any order, as well as strip away most of the meta-programming. But that is a lot more to type.
I have some difficulties for using Ruby block, passing in a method.
As in the following case, I would like to display each element of #array, from Box instance (using .each method):
class Box
def initialize
#array = [:foo, :bar]
end
def each(&block)
# well, hm..
end
end
a = Box.new
a.each { |element| puts element }
You really just need to delegate to the each method on #array and pass it the block. Additionally, you can include the Enumerable mix-in to gain access to the methods it provides (e.g. map, inject, etc...):
class Box
include Enumerable
def initialize
#array = [:foo, :bar]
end
def each(&block)
#array.each(&block)
end
end
More information on the Enumerable module is available in the documentation.
For this simple example, you actually aren't required to pass the block explicitly:
def each
#array.each{|e| yield e}
end
Passing the block (which is a Proc object) explicitly allows you to test it for things, like the number of arguments that it expects:
class Box
...
def each(&block)
#array.each do |e|
case block.arity
when 0
yield
when 1
yield e
when 2
yield e, :baz
else
yield
end
end
end
end
a = Box.new
a.each { puts "nothing" } # displays "nothing, nothing"
a.each { |e| puts e } # displays "foo, bar"
a.each { |e1, e2| puts "#{e1} #{e2}" } # displays "foo baz, bar baz"