Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby method with optional options and &block parameter

  1. Hey there

    Is it possible to have optional attributes and a block as parameters for a method call?

    Example: I have to call

    method(foo, foo: bar, -> { do_something }
    

    and tried it with

    def method(foo, *bar, &block)
    end
    

    As for my understanding the block always has to be at last position?

    After a bit of research I found out the unary(?) * seems to be for arrays. Since I try to pass a Hash I changed the code to

    def method(foo, bar={}, &block)
    end
    

    But this doesn't do the trick either. I guess its because he cant figure out where the bar ends and the block starts.

    Any ideas or suggestions? Thank you in advance

    Append: Just for the curious why I need this. We have a big json schema running and have a small DSL that builds the json from the model definitation. Without going to much into detail we wanted to implement exportable_scopes.

    class FooBar
      exportable_scope :some_scope, title: 'Some Scope', -> { rewhere archived: true }
    end
    

    On some initializer this is supposed to happens:

    def exportable_scope scope, attributes, &block
      scope scope block
      if attributes.any?
        attributes.each do |attribute|
          exportable_schema.scopes[scope] = attribute
        end
      else
        exportable_schema.scopes[scope] = {title: scope}
      end
    end
    

    So this is working fine, I just need a hint for the method parameters.

like image 571
Denny Mueller Avatar asked Feb 06 '23 11:02

Denny Mueller


1 Answers

Yes, it is possible.

When mixing different kinds of parameters, they have to be included in the method definition in a specific order:

  1. Positional parameters (required and optional) and a single splat parameter, in any order;
  2. Keyword parameters (required and optional), in any order;
  3. Double splat parameter;
  4. Block parameter (prefixed with &);

The order above is somewhat flexible. We could define a method and begin the parameter list with a single splat argument, then a couple of optional positional arguments, and so on. Even though Ruby allows that, it's usually a very bad practice as the code would be hard to read and even harder to debug. It's usually best to use the following order:

  1. Required positional parameters;
  2. Optional positional parameters (with default values);
  3. Single splat parameter;
  4. Keyword parameters (required and optional, their order is irrelevant);
  5. Double splat parameter;
  6. Explicit block parameter (prefixed with &).

Example:

def meditate cushion, meditation="kinhin", *room_items, time: , posture: "kekkafuza", **periods, &b
    puts "We are practicing #{meditation}, for #{time} minutes, in the #{posture} posture (ouch, my knees!)."
    puts "Room items: #{room_items}"
    puts "Periods: #{periods}"
    b.call # Run the proc received through the &b parameter
end

meditate("zafu", "zazen", "zabuton", "incense", time: 40, period1: "morning", period2: "afternoon" ) { puts "Hello from inside the block" }

# Output:
We are practicing zazen, for 40 minutes, in the kekkafuza posture (ouch, my knees!).
Room items: ["zabuton", "incense"]
Periods: {:period1=>"morning", :period2=>"afternoon"}
Hello from inside the block

Notice that when calling the method, we have:

  • Provided the cushion mandatory positional argument;
  • Overwritten the default value of the meditation optional positional argument;
  • Passed a couple of extra positional arguments (zabuton and incense) through the *room_items parameter;
  • Provided the time mandatory keyword argument;
  • Omitted the posture optional keyword argument;
  • Passed a couple of extra keyword arguments (period1: "morning", period2: "afternoon") through the **periods parameter;
  • Passed the block { puts "Hello from inside the block" } through the &b parameter;

Please note the example above servers only to illustrate the possibility of mixing different types of parameters. Building a method like this in real code would be a bad practice. If a method needs that many arguments, it's probably best to split it into smaller methods. If it's absolutely necessary to pass that much data to a single method, we should probably create a class to store the data in a more organized way, then pass an instance of that class to the method as a single argument.

like image 54
BrunoF Avatar answered Feb 19 '23 01:02

BrunoF