Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 
>> ....
like image 987
EEE Avatar asked Apr 15 '21 18:04

EEE


People also ask

Why use method chaining?

Method chaining eliminates an extra variable for each intermediate step. The developer is saved from the cognitive burden of naming the variable and keeping the variable in mind.

What is method chaining in laravel?

Method chaining interface allows you to chain method calls, which results in less typed characters when applying multiple operations on the same object. Using method chaining.


Video Answer


4 Answers

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) }
like image 80
spickermann Avatar answered Oct 23 '22 08:10

spickermann


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] >>
  # ...
)
like image 29
3limin4t0r Avatar answered Oct 23 '22 09:10

3limin4t0r


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.

like image 22
Cary Swoveland Avatar answered Oct 23 '22 08:10

Cary Swoveland


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.

like image 21
Ben Stephens Avatar answered Oct 23 '22 10:10

Ben Stephens