Home > Net >  Ruby 3 Dynamic Keyword Arguments
Ruby 3 Dynamic Keyword Arguments

Time:11-08

I have some code that looked like this in Ruby2.7


#assume data is either an ActiveRecord::Relation or a plain array. If it's an ActiveRecord, use :find_each instead, but set batch_size => 5000

if data.respond_to?(:find_each)
  method_to_call = :find_each
  arguments_to_send = [{:batch_size => 5_000}]
else
  method_to_call = :each
  arguments_to_send = []
end

data.__send__(method_to_call, *arguments_to_send) do |item|
 #do stuff in each iteration 
end

However this code no longer works in Ruby3 as keyword arguments can't be forwarded like that.

How can I accomplish the same using metaprogramming in Ruby3?

CodePudding user response:

This is due to the separation of positional and keyword arguments in Ruby 3.0 (see https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/)

Here is what the Ruby 3 version of your code would look like:

if data.respond_to?(:find_each)
  method_to_call = :find_each
  arguments_to_send = []
  keyword_arguments_to_send = {:batch_size => 5_000}
else
  method_to_call = :each
  arguments_to_send = []
  keyword_arguments_to_send = {}
end

data.__send__(method_to_call, *arguments_to_send, **keyword_arguments_to_send) do |item|
 # do stuff in each iteration 
end

The code above is the more generic version that handles both regular arguments and keyword arguments. In your case, find_each takes only keyword arguments (at least in Rails 7 -- I didn't check earlier versions) and each takes no arguments, so you could get away with this if you wanted:

if data.respond_to?(:find_each)
  method_to_call = :find_each
  keyword_arguments_to_send = {:batch_size => 5_000}
else
  method_to_call = :each
  keyword_arguments_to_send = {}
end

data.__send__(method_to_call, **keyword_arguments_to_send) do |item|
 # do stuff in each iteration 
end
  • Related