I'd like to be able to dynamically combine scopes at runtime, to create a custom query based on user input. Say I have a library application with a Book model, and I have the following scopes defined: checked_out, fiction, non_fiction, overdue, and checked_out_by
.
The last scope
, checked_out_by
, is a lambda that takes a library_user_id as an argument. The application allows the librarian to run different kinds of queries that combine these scopes dynamically, based on user input. So if the librarian wants to create a report showing all non-fiction books that are checked out, it's easy to do something like this:
criteria = ["non_fiction", "checked_out"] <= array built based on what the user selected
books = Book.scoped
criteria.each {|criteria| books = books.send(criteria)}
But how do I do this with the scope that is set up as a lambda? For instance, if the user wants to create a query showing books that are overdue and checked_out_by a particular person? In this case, I'd want the criteria array to contain the "overdue" and "checked_out_by" scopes, but I can't figure out how to set that up with the lambda scope. I tried:
criteria = ["non_fiction", {:checked_out_by => 6}]
but that gives me an error saying "{:checked_out_by => 6} is not a symbol
". I also tried
criteria = ["non_fiction", [checked_out_by, 6]]
and a few other variants, but none of them worked. Does anyone know how to do this?
You can send parameters as the second parameter to send
. Building on your example, you could do something like this:
criteria = [:non_fiction, { checked_out_by: 6 }]
criteria.each do |c|
case c
when Hash then c.keys.each { |key| books = books.send key, c[key] }
when Symbol then books = books.send c
end
end
Or you can use an OpenStruct
in place of the Hash
.
criteria = [:non_fiction, OpenStruct.new(key: :checked_out_by, value: 6)]
criteria.each do |c|
books = case c
when OpenStruct then books.send c.key, c.value
when Symbol then books.send c
end
end
Or, better yet, create an class that has the key and value that you need.