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