EEE
EEE

Reputation: 149

Ruby custom method chaining

What is the best way of chaining multiple custom methods together? I want to put the output of my method directly into the next method in an elegant way. The first one below is what I have now.

verified = search_verified(@providers)
matching = search_matching(verified)
deactivated = search_deactivated(matching)
networks = search_networks(deactivated)
designations = search_designations(networks)
disorders = search_disorders(designations)
age_groups = search_age_groups(disorders)
governing = search_governing(age_groups)
search_availabilities(governing)

maybe something more along the lines of:

search_verified(@providers) 
>> search_matching
>> search_deactivated
>> search_networks 
>> ....

Upvotes: 5

Views: 630

Answers (6)

3limin4t0r
3limin4t0r

Reputation: 21110

You can do something along those lines. Ruby 2.6.0 introduces then, and the function composition operators << and >>.

You do have to select your methods with method(:method_name) because method_name by itself invokes the method and does not return it.

@providers.then(&
  method(:search_verified)     >>
  method(:search_matching)     >>
  method(:search_deactivated)  >>
  method(:search_networks)     >>
  method(:search_designations) >>
  method(:search_disorders)    >>
  method(:search_age_groups)   >>
  method(:search_governing)    >>
  method(:search_availabilities)
)

If you don't like the character "overhead". You could shrink the amount of characters by storing the method method in a shorter variable first:

fn = method(:method)

@providers.then(&
  fn[:search_verified] >>
  # ...
)

Upvotes: 5

Ben Stephens
Ben Stephens

Reputation: 3371

I've not actually tried this other than a quick attempt in the console, but from looking at: https://andersmurphy.com/2019/12/07/ruby-functional-programming.html it looks like something like the following should be possible:

pipe = -> *fns {fns.reverse.reduce {|f, g| -> x {f.(g.(x))}}}
add_one = -> x {x + 1}
times_two = -> x {x * 2}

add_one_and_times_two = pipe.(add_one, times_two)

add_one_and_times_two.(2)
=> 6

pipe.(add_one, times_two).(3)
=> 8

If you want to use this with methods you can possibly (this seems to work in the console) do something like:

def divide_by_three(x); x / 3.0 end

pipe.(
  add_one,
  times_two,
  method(:divide_by_three)
).(4)
=> 3.3333333333333335

using the method function as shown in @3limin4t0r's answer.

Upvotes: 1

peter
peter

Reputation: 42182

If all the methods are in one class you can chain these methods by returning self in each method. For the sake of clarity I take your example but the providers are just numbers.

class MyClass

  def initialize
    @@providers = [2, 6, 4, 8, 7]
  end
  
  def search_matching 
    # do stuff
    @@providers.select!{ |n| n > 3 }
    self
  end

  def search_deactivated
    # do other stuff
    @@providers.select!{ |n| n < 8 }
    self
  end

  def providers
    @@providers
  end
end

MyClass.new.search_matching.search_deactivated.providers # [6, 4, 7]

Upvotes: 0

Cary Swoveland
Cary Swoveland

Reputation: 110665

You could write that as follows.

METHODS = [:verified, :matching, :deactivated, :networks, :designations,
           :disorders, :age_groups, :governing, :search_availabilities]
def doit(first_arg)
  METHODS.reduce(first_arg) { |arg, meth| send(meth, arg) } 
end

This would be called

doit(@providers)

For example:

def a(arg)
  arg * 2
end
def b(arg)
  arg + 1
end
def c(arg)
  arg/5.0
end
METHODS = [:a, :b, :c]
doit(3)
  #=> 1.4

One could alternatively write

def doit(first_arg)
  METHODS.reduce(first_arg) { |arg, meth| method(meth).call(arg) } 
end
doit(3)
  #=> 1.4

One advantage of this approach is that if methods are added, removed or renamed is is only necessary to change the constant METHODS; the method doit is unaffected.

Upvotes: 2

spickermann
spickermann

Reputation: 106792

You might want to use then to chain your methods and numbers parameter to simplify the blocks:

search_verified(@providers)
  .then { search_matching(_1) }
  .then { search_deactivated(_1) }
  .then { search_networks(_1) }
  .then { search_designations(_1) }
  .then { search_disorders(_1) }
  .then { search_age_groups(_1) }
  .then { search_governing(_1) }
  .then { search_availabilities(_1) }

Upvotes: 6

Carlo Osores
Carlo Osores

Reputation: 1

it depends on what data do you really need and how you define the architecture of your code, i usually make a Service object.

if you only want to return only the last method output. return search_availabilities(governing).

If you need all variables, you can make it return an array with all the variables or the ones that you need. return [verified, matching, deactivated, ...].

Upvotes: -2

Related Questions