3

Here is what I have

currently, I'm doing this:

current_account.send(object).search(params[:search]).user_id_equals_any(users).visibility_is_any(visibilities)

but thats not very flexible? What if I want to conditionally not have one of those scopes?

I figure an array of scopes with conditionally added elements would be a good solution

An array of parameters that I would invoke using .send()

scopes = []    
scopes = << [:user_id_equals_any, users] if filter_users
    scopes = << [:visibility_is_any, visibilities] if filter_visibility

So, I could have some unknown number scopes.

To execute the scopes, I need to invoke .send(:scope_name, scope_param)

But How do I do that for any number of scopes in my scopes array?

As a loop, I think It would be something like

result = current_account.send(object).search(params[:search])
scopes.each do |scope|
    result.send(scope[0], scope[1])
end
return result

the loop can be simplified to

scopes.each {|s| result.send(s[0], s[1]) }

but is there a way to append the different send calls in one line?

Holger Just
  • 52,918
  • 14
  • 115
  • 123
NullVoxPopuli
  • 61,906
  • 73
  • 206
  • 352

2 Answers2

3

You can use inject:

scopes = []
# Note that in your example, the assignment is invalid syntax
scopes << [:user_id_equals_any, users] if filter_users
scopes << [:visibility_is_any, visibilities] if filter_visibility

# initial result
result = current_account.send(object).search(params[:search])

# applying the scopes
result = scopes.inject(result) do |result, (method, param)|
  result.send(method, param)
end
return result

In each loop, the last return value of the previous block execution gets passed the first parameter of the block, in this case result. The first loop gets the initial result (i.e. the parameter in square brackets to the inject call. The return value of the last loop gets returned.

For more details, have a look at the documentation.

Holger Just
  • 52,918
  • 14
  • 115
  • 123
1

Feel free to build your own abstractions so your code reflects how you think about a problem:

class Object
  def send_if(condition, *args, &block)
    condition ? send(*args, &block) : self
  end
end

current_account.send(object).search(params[:search]).
  send_if(filter_user, :user_id_equals_any, users).
  send_if(filter_visibility, :visibility_is_any, visibilities)
tokland
  • 66,169
  • 13
  • 144
  • 170
  • why is the third param a block?I thought send was just `.send(name, params)` – NullVoxPopuli Jun 15 '12 at 20:38
  • @TheLindyHop: you may want to send a block to the wrapped method, it's just to make it more generic even though it's not being used in the example. – tokland Jun 15 '12 at 20:39
  • Could you provide an example of how that would be used? the original .send doesn't take a block http://ruby-doc.org/core-1.9.3/Object.html#method-i-send – NullVoxPopuli Jun 15 '12 at 20:42
  • 1
    example with _squeel_ (AR wrapper): `User.send_if(condition, :where) { id > 1 }.all`. I don't know why the documentation shows no block for `send` (maybe it's implicit in `args...`) but rest assured you can pass a block to `send` and it will be passed to the object, otherwise it'd be pretty useless. – tokland Jun 15 '12 at 20:46
  • I'm not sure if this solution is really better than mine. You still have long call chains (which even span three lines) and you "pollute" every object with your method. While it is indeed rather generic, it still doesn't allow to prepare the `scopes` array somewhere and then just apply it in a single location. You still "hardcode" the calls and parameters. – Holger Just Jun 15 '12 at 20:48
  • @Holger: I was just providing an alternative, I am also surprised it got chosen. In fact I use your scopes+inject a lot. Regarding the "pollution" of `Object`... have you taken a look at the bunch of method already there? :-) IMHO it's not against Ruby's philosophy to add methods to base classes as long as they are generic. That's a great thing of Ruby, it allows to build your own abstractions, you're not constraint by the core devs choices. – tokland Jun 15 '12 at 20:50
  • @tokland: A basic Ruby 1.8.7 has 45 instance methods on `Object`, the majority from `Kernel`, only when using Rails, that number grows significantly. Also, while I do like to extend base classes with nifty functionality when it's justified, I *really* look forward for the [refinements](http://bugs.ruby-lang.org/issues/4085) (hopefully) available with Ruby 2.0 which will allow scoped inclusion of modules, preventing the pollution of the global namespace for everyone. – Holger Just Jun 15 '12 at 21:07
  • @HolgerJust: Thanks for the link to refinements, I wasn't aware of that proposal. Object method count for 1.9.3 went up to 63 :-( – tokland Jun 15 '12 at 21:33