Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering ActiveRecord queries in rails

Tags:

I'm used to Django where you can run multiple filter methods on querysets, ie Item.all.filter(foo="bar").filter(something="else").

The however this is not so easy to do in Rails. Item.find(:all, :conditions => ["foo = :foo", { :foo = bar }]) returns an array meaning this will not work:

Item.find(:all, :conditions => ["foo = :foo", { :foo = 'bar' }]).find(:all, :conditions => ["something = :something", { :something = 'else' }])

So I figured the best way to "stack" filters is to modify the conditions array and then run the query.

So I came up with this function:

 def combine(array1,array2)    conditions = []    conditions[0] = (array1[0]+" AND "+array2[0]).to_s    conditions[1] = {}    conditions[1].merge!(array1[1])    conditions[1].merge!(array2[1])    return conditions  end 

Usage:

array1 = ["foo = :foo", { :foo = 'bar' }] array2 = ["something = :something", { :something = 'else' }] conditions = combine(array1,array2) items = Item.find(:all, :conditions => conditions)

This has worked pretty well. However I want to be able to combine an arbitrary number of arrays, or basically shorthand for writing:

conditions = combine(combine(array1,array2),array3) 

Can anyone help with this? Thanks in advance.

like image 810
user94154 Avatar asked Jun 01 '09 21:06

user94154


People also ask

What is ActiveRecord in Ruby on Rails?

Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database.

Is ActiveRecord an ORM?

ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code. When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database.

What is eager loading rails?

Eager loading solves this problem by creating a left outer join on the table, returning all of the data in a single query. Adding an eager load is as simple as adding an :includes statement to the controller.


2 Answers

What you want are named scopes:

class Item < ActiveRecord::Base   named_scope :by_author, lambda {|author| {:conditions => {:author_id => author.id}}}   named_scope :since, lambda {|timestamp| {:conditions => {:created_at => (timestamp .. Time.now.utc)}}}   named_scope :archived, :conditions => "archived_at IS NOT NULL"   named_scope :active, :conditions => {:archived_at => nil} end 

In your controllers, use like this:

class ItemsController < ApplicationController   def index     @items = Item.by_author(current_user).since(2.weeks.ago)     @items = params[:archived] == "1" ? @items.archived : @items.active   end end 

The returned object is a proxy and the SQL query will not be run until you actually start doing something real with the collection, such as iterating (for display) or when you call Enumerable methods on the proxy.

like image 99
François Beausoleil Avatar answered Oct 09 '22 07:10

François Beausoleil


I wouldn't do it like you proposed.

Since find return an array, you can use array methods to filter it, on example:

Item.find(:all).select {|i| i.foo == bar }.select {|i| i.whatever > 23 }... 

You can also achive what you want with named scopes.

like image 45
klew Avatar answered Oct 09 '22 08:10

klew