简体   繁体   中英

Rails find conditions… where attribute is not a database column

I think it's safe to say everyone loves doing something like this in Rails:

Product.find(:all, :conditions => {:featured => true})

This will return all products where the attribute "featured" (which is a database column) is true. But let's say I have a method on Product like this:

def display_ready?
     (self.photos.length > 0) && (File.exist?(self.file.path))
end

...and I want to find all products where that method returns true. I can think of several messy ways of doing it, but I think it's also safe to say we love Rails because most things are not messy.

I'd say it's a pretty common problem for me... I'd have to imagine that a good answer will help many people. Any non-messy ideas?

The only reliable way to filter these is the somewhat ugly method of retrieving all records and running them through a select:

display_ready_products = Product.all.select(&:display_ready?)

This is inefficient to the extreme especially if you have a large number of products which are probably not going to qualify.

The better way to do this is to have a counter cache for your photos, plus a flag set when your file is uploaded:

class Product < ActiveRecord::Base
  has_many :photos
end

class Photo < ActiveRecord::Base
  belongs_to :product, :counter_cache => true
end

You'll need to add a column to the Product table:

add_column :products, :photos_count, :default => 0

This will give you a column with the number of photos. There's a way to pre-populate these counters with the correct numbers at the start instead of zero, but there's no need to get into that here.

Add a column to record your file flag:

add_column :products, :file_exists, :boolean, :null => false, :default => false

Now trigger this when saving:

class Product < ActiveRecord::Base
  before_save :assign_file_exists_flag

protected
  def assign_file_exists_flag
    self.file_exists = File.exist?(self.file.path)
  end
end

Since these two attributes are rendered into database columns, you can now query on them directly:

Product.find(:all, :conditions => 'file_exists=1 AND photos_count>0')

You can clean that up by writing two named scopes that will encapsulate that behavior.

You need to do a two level select:

1) Select all possible rows from the database. This happens in the db.

2) Within Ruby, select the valid rows from all of the rows. Eg

possible_products = Product.find(:all, :conditions => {:featured => true})
products = possible_products.select{|p| p.display_ready?}

Added:

Or:

products = Product.find(:all, :conditions => {:featured => true}).select {|p|
               p.display_ready?}

The second select is the select method of the Array object. Select is a very handy method, along with detect. (Detect comes from Enumerable and is mixed in with Array.)

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