How to dynamically create methods with parameter in ruby?
You should use public_send
to call methods based on their name:
['id', 'message', 'votes_count'].each do |method|
define_method "#{method}" do |parameter|
parameter.public_send(method)
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
Dynamic method calling with arguments
What you are trying to do can be really dangerous, so I recommend you filter the params[:method]
before.
allowed_methods = {
method_one: ->(ex){ex.method_one(params[:value])}
method_two: ->(ex){ex.method_two}
}
allowed_methods[params[:method]]&.call(ex)
I defined an Hash mapping the methods name to a lambda calling the method, which handles arguments and any special case you want.I only get a lambda if params[:method]
is in the allowed_methods
hash as a key.
The &.
syntax is the new safe navigation operator in ruby 2.3, and - for short - executes the following method if the receiver is not nil (i.e. the result of allowed_methods[params[:method]]
)
If you're not using ruby >= 2.3, you can use try
instead, which have a similar behavior in this case :
allowed_methods[params[:method]].try(:call, ex)
If you don't filter the value of params[:method]
, then a user can just pass :destroy
for example to delete your entry, which is certainly not what you want.Also, by calling ex.send ...
, you bypass the object's encapsulation, which you usually shouldn't. To use only the public interface, prefer using public_send
.
Another point on the big security flaw of you code:
eval
is a private method defined on Object
(actually inherited from Kernel
), so you can use it this way on any object :
object = Object.new
object.send(:eval, '1+1') #=> 2
Now, with your code, imagine the user puts eval
as the value of params[:method]
and an arbitrary ruby code in params[:value]
, he can actually do whatever he wants inside your application. ruby / Passing arguments to a dynamically created class
It is unclear why you want to create the class in the first place. If there are good reasons for it, my answer is kind of invalid.
You can have the desired behaviour using "standard" OOP techniques and working with instances
class Fetcher
def initialize(resource_name)
@resource_name = resource_name
end
def all
"http://some.remote.resource/#{@resource_name}/all"
end
end
xyz_fetcher = Fetcher.new('xyz')
xyz_fetcher.all
Otherwise, your code is more or less what you would/should do, I guess. Just, I would let the Fetcher
class act as a singleton (not use an instance of Fetcher
):class Fetcher < ApplicationService
# make a singleton by privatizing initialize (read this up somewhere else)
def self.build(resource_name)
Class.new(BaseClass) do
@@resource_name = resource_name
class << self
def all
"http://some.remote.resource/#{@@resource_name}/all"
end
end
end
end
end
ThenXyz = Fetcher.build('xyz')
Xyz.all
Now, you have the stuff with ApplicationService
which more or less achieves that (and passes a block), so probably we readers miss some parts of the bigger picture ... please clarify if that is the case.Besides singletonization, you could also work with modules instead (thanks @max for the comment).
How to dynamically create instance methods at runtime?
I'm particularly fond of using method_missing
, especially when the code you want to use is very similar across the various method calls. Here's an example from this site - whenever somebody calls x.boo
and boo
doesn't exist, method_missing is called with boo
, the arguments to boo
, and (optionally) a block:
class ActiveRecord::Base
def method_missing(meth, *args, &block)
if meth.to_s =~ /^find_by_(.+)$/
run_find_by_method($1, *args, &block)
else
super # You *must* call super if you don't handle the
# method, otherwise you'll mess up Ruby's method
# lookup.
end
end
def run_find_by_method(attrs, *args, &block)
# Make an array of attribute names
attrs = attrs.split('_and_')
# #transpose will zip the two arrays together like so:
# [[:a, :b, :c], [1, 2, 3]].transpose
# # => [[:a, 1], [:b, 2], [:c, 3]]
attrs_with_args = [attrs, args].transpose
# Hash[] will take the passed associative array and turn it
# into a hash like so:
# Hash[[[:a, 2], [:b, 4]]] # => { :a => 2, :b => 4 }
conditions = Hash[attrs_with_args]
# #where and #all are new AREL goodness that will find all
# records matching our conditions
where(conditions).all
end
end
define_method
also looks like it would work for you, but I have less experience with it than method_missing
. Here's the example from the same link:%w(user email food).each do |meth|
define_method(meth) { @data[meth.to_sym] }
end
Ruby Default Arguments: Static or Dynamic?
It is dynamic because Ruby is interpreted, not compiled language.
✎ require 'date'
✎ def test param = DateTime.now
puts param
end
✎ 3.times { test; sleep(1) }
2018-12-14T18:10:08+01:00
2018-12-14T18:10:09+01:00
2018-12-14T18:10:10+01:00
Related Topics
How to Read Text from Non Visible Elements with Watir (Ruby)
Capybara & Cucumber | Getting Cookies
Ruby-Open3.Popen3/How to Print The Output
How to Get All Message History from Hipchat for a Room via The API
Docker-Compose Restart Connection Pool Full
What Is Returned in Ruby If The Last Statement Evaluated Is an If Statement
Undefined Method 'Merge' for '####':String <%= Form_For %> Helper
Can Optionparser Skip Unknown Options, to Be Processed Later in a Ruby Program
How to Remove Backslashes from a JSON String
Using Rails Form Helpers with Serialized Custom Classes
What's an Example of Ruby Code That's "Too Clever"
Generate Models from Existing Tables Using Rails 3
Sorting a Hash in Ruby Based on Value and Then Key
How to Send a Keep-Alive Packet Through Websocket in Ruby on Rails
Ruby/Active Record: Custom Sorting Order
Rails How to Create Data Schema Seed Data
Fresh Ruby Gem from Bundler - Cannot Load My Version.Rb File