Make any order chainable
I want to sort my Person
model objects based on a complicated开发者_JS百科 criterion that can't be summarized in a single query - and so in a named scope. Right now I use a class method like:
def Person.very_complicated_sorting
Person.all.sort { |x,y| x.bunch_of_calculations <=> y.bunch_of_calculations }
end
Is there a way to make this chainable? E.g.
Person.tallest.very_complicate_sorting.youngest
where tallest
and youngest
are two named scopes.
This isn't possible unfortunately.
The way named scopes work is by "lazily" building up a combined set of SQL parameters, which aren't evaluated until you actually try to do something with them. So for example the following chain of named scopes:
people = Person.tallest.youngest
will not cause any database query to be run, it actually causes an ActiveRecord::NamedScope
object to be stored in the people
variable. Only when you access or iterate through that object is the SQL run and the objects loaded.
Your problem is that your sorting method isn't being expressed in SQL, it's a set of Ruby conditions. When Rails gets to your sort_by
it has to go and fetch and instantiate the Person
objects so that it can run your condition on them. After it's done that you have an Array
of objects and not a NamedScope
object any more.
Your very_complicated_sorting
should be in your Person
model.
You musn't not write Person.all
inside the method, it couldn't be chainable otherwise.
Just keep:
def self.very_complicated_sorting
sort_by { |x,y| x.bunch_of_calcultaions <=> y.bunch_of_calculations }
end
This is possible if you use Sequel instead of ActiveRecord for your database library. It lets you use def_dataset_method
on Models to create methods that operate on Datasets (of which Models are included); as long as your methods return a Dataset then you can chain them as you like. For example:
class Person < Sequel::Model
def_dataset_method :active_only do
filter :active=>true
end
def_dataset_method :sort_much do
order :name, :age.desc
end
end
active_peeps = Person.active_only.sort_much
peeps_active = Person.sort_much.active_only
To stay in Dataset land requires you to only use methods that can be expressed as SQL. You cannot expect to ask for some records from the database, then perform complex Ruby-only logic on them (say, sorting them by their object_id
looked up in a Ruby Hash), and then continue to perform SQL on the resulting array. Ruby does not run on your database (other than pl/Ruby).
Alternatively, you can go straight to a Ruby array and then chain whatever Ruby methods you like in any order, if they all operate on arrays and return arrays. You must make a clear decision in your code as to when you are going to fetch the results from the Database and then operate on the array afterwards.
精彩评论