Define_Method with Predefined Keyword Arguments

define_method with predefined keyword arguments

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]]

How do you pass arguments to define_method?

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 }

Metaprogrammatically defining Ruby methods that take keyword arguments?

I wound up using a (surprisingly Pythonic) **kwargs strategy, thanks to the new features in Ruby 2.0+:

module StricterStruct
def self.new(*attribute_names_as_symbols)
c = Class.new
l = attribute_names_as_symbols

c.instance_eval {
define_method(:initialize) do |**kwargs|
unless kwargs.keys.sort == l.sort
extra = kwargs.keys - l
missing = l - kwargs.keys

raise ArgumentError.new <<-MESSAGE
keys do not match expected list:
-- missing keys: #{missing}
-- extra keys: #{extra}
MESSAGE
end

kwargs.map do |k, v|
instance_variable_set "@#{k}", v
end
end

l.each do |sym|
attr_reader sym
end
}

c
end
end

define_method: How to dynamically create methods with arguments

If I understand your question correctly, you want something like this:

class Product
class << self
[:name, :brand].each do |attribute|
define_method :"find_by_#{attribute}" do |value|
all.find {|prod| prod.public_send(attribute) == value }
end
end
end
end

(I'm assuming that the all method returns an Enumerable.)

The above is more-or-less equivalent to defining two class methods like this:

class Product
def self.find_by_name(value)
all.find {|prod| prod.name == value }
end

def self.find_by_brand(value)
all.find {|prod| prod.brand == value }
end
end

Dynamically set parameters in define_method

So as advised I went to class_eval.

class Whatever
class << self
def add_method(name, parameters = {})
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}(#{method_parameters(parameters)})
#{method_body(parameters)}
end
RUBY
end

# method_parameters({
# foo: { required: false, default: 0 },
# bar: { required: true }
# })
# => "foo: 0, bar:"
def method_parameters(parameters)
parameters.map do |key, options|
value = options[:required] ? '' : " #{options[:default] || 'nil'}"
"#{key}:#{value}"
end.join(', ')
end

# method_parameters({
# foo: { required: false, default: 0 },
# bar: { required: true }
# })
# => "[foo, bar]"
def method_body(parameters)
"[#{parameters.keys.map(&:to_s).join(', ')}]"
end
end
end
params = {
foo: { required: false, default: 0 },
bar: { required: true }
}

Whatever.add_method(:hello, params)

Whatever.new.hello(bar: true) # => [0, true]
Whatever.new.hello(foo: 42, bar: true) # => [42, true]
Whatever.new.hello # missing keyword: bar (ArgumentError)

Ruby: Send define_method with block arguments

You have few issues, like using reserved words, not understanding the self (or rather not implementing your understanding correctly).

source.attribute_names.each do |attribute|
source.singleton_class.instance_eval do
define_method('next_by_' + attribute) do |object, user = nil|
if user
source.joins(:users)
.where("#{attribute} > ?", object.public_send(attribute))
.where(users: { id: user.id })
.first
else
source.where("#{attribute} > ?", object.public_send(attribute)).first
end
end
end
end

define_method in Python

What happens is that you set e.g. 'with_red' attribute on your MyColor instance to a local function defined in Base constructor - note that this is not a class method, just a function, and it takes 2 arguments: 'self' and 'value':

import inspect
...
s = MyColor()
print(inspect.getargspec(s.with_red))

ArgSpec(args=['self', 'value'], varargs=None, keywords=None, defaults=None)

An easy fix here would be to make this function take a single argument:

def _with_field(value):
self.colors[field] = value
return self

With this change your code produces the expected output.

Another option is to set 'with_red' attribute on the class - which makes it a method, then self is passed implicitly and you can keep _with_field signature with two arguments.



Related Topics



Leave a reply



Submit