简体   繁体   中英

How do I combine a .select to execute two Procs?

Here is the code:

params[:controller_id].select(&check_if_controller_exists)
params[:controller_id].select(&check_controller_permissions)

This works. I have an array, and I'm converting a Proc into a block.

What I don't know how to do is combine them - some might say this is more appropriate for code-review, but in my opinion I have a question about how to write some code.

Is it possible to combine the above without modifying the individual Procs? Or am I supposed to just combine into one Super Proc instead of having two distinct blocks?

More information was requested:

def check_if_controller_exists # in case they pass an array of controllers
  Proc.new { |c| raise CanCan::AccessDenied.new("One or more of the specified controllers does not exist for this site") unless Controller.find_by_id(c) }
end

def check_controller_permissions # in case they pass an array of controllers
  Proc.new { |c| raise CanCan::AccessDenied unless can? :read, Controller.find_by_id(c) } 
end

Like most programming, this construct evolved out of moving one of the exceptions that was duplicated out, then later moving the other, then seeing that I have two which are essentially the same, and wanting to merge them into a single call, but as they had already been rendered as Procs, I stumble on wondering how to merge Procs.

That is technically the goal for this post - to learn how to merge a couple Procs.

Clearly I shouldn't need to hit the DB multiple times for these two things.

Or you could avoid the Symbol#to_proc conversion and use

params[:controller_id].select do |p| 
  p.check_if_contoller_exists && p.check_controller_permissions
end

provides the same functionality. Symbol#to_proc has been proven to be faster but I am not sure if it would be for multiple iterations maybe I'll benchmark it.

Okay so even in back to back calls Symbol#to_proc is faster.

require 'benchmark'
a = (1..500).to_a
num_times = 1000
proc1 = proc {|a| a.to_s.is_a?(String)}
proc2 = proc {|a| a.to_s.to_i == a}

Benchmark.bm do |x|
  x.report('Symbol#to_proc') { num_times.times{ a.select(&proc1).select(&proc2) } }
  x.report('block') { num_times.times{ a.select{ |e| proc1.call(e) && proc2.call(e) } } }
end

#=>                      user     system      total        real
        Symbol#to_proc   0.343000   0.000000   0.343000 (  0.343475)
        block            0.453000   0.000000   0.453000 (  0.445722)

If by "combine" you mean get the result of && 'ing their results, you could just:

result = params[:controller_id].select(&check_if_controller_exists)
                               .select(&check_controller_permissions)

It will spin through the enumeration twice, which may or may not be a problem depending on your situation.

If we're merely code reviewing you, my first suggestion would be two write one method that checks both conditions at the same time. This way you only iterate over your params Hash once.

result = params[:controller_id].select(&:check_controller_presence_and_permissions)

private
def check_controller_presence_and_permissions
  check_if_controller_exists && check_controller_permissions
end

That said, its hard for me to offer good advice without knowing the implementations of check_if_controller_exists or check_controller_permissions . If you could edit your Question and include more of your code, we can probably help you refactor this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM