ruby-on-railsruby

Handle an array as a Ruby method would


I'm well aware of Ruby's splats and double-splats in methods

def method(*arg, **kwarg)
  :
end

method('foo', bar: 'baz') # arg = ['foo'], kwarg = { bar: 'baz' }

but how would one do that processing with an array which looks like those argument, i.e.,

['foo', { bar: 'baz' }]

I can see a heavy-handed check if the last argument is a hash etc, etc, but this is Ruby, surely there is an elegant and economical one-liner for this.

Motivation:

I'm writing an ActiveJob which destroys an "ingestion", I'm doing that as a job since it takes a couple of minutes to run (a cascade of DB deletions of assets associated with it). So I have in that job

def perform(ingestion:)
  ingestion.destroy!
end

Now, once this job has been enqueued, I'd want the front-end to know that this is the case, so I've added a deleted_at attribute and a #delete_pending! method which sets it to now; I want to call that as soon as the job is enqueued, and ActiveJob has callbacks which are a good place to do that:

before_enqueue do |job|
  :
end

and in that block, job.arguments is an array of the form of the arguments passed to the perform. Hence my question.

For completeness, using the accepted answer (for which, many thanks) my callback now looks like

  before_enqueue do |job|
    job
      .arguments
      .clone
      .extract_options!
      .fetch(:ingestion)
      .delete_pending!
  end

the #clone needed since #extract_options! modifies the caller, and I don't want that.


Solution

  • this is Ruby, surely there is an elegant and economical one-liner for this.

    There isn't. This is why Rails makes use of extract_options! to pull the final hash out of an array of arguments, you probably want to do the same.

    In your case:

    def foo_method(*args)
      opts = args.extract_options!
      puts "args: #{args}"
      puts "opts: #{opts}"
    end
    
    args = ['foo', { bar: 'baz' }]
    foo_method(*args)
    # args: ["foo"]
    # opts: {:bar=>"baz"}