简体   繁体   中英

Rails-y way to query a model with a belongs_to association

I have two models:

class Wine
  belongs_to :region
end

class Region
  has_many :wines
end

I am attempting to use the #where method with a hash built from transforming certain elements from the params hash into a query hash, for example { :region => '2452' }

def index
  ...
  @wines = Wine.where(hash)
  ...
end

But all I get is a column doesn't exist error when the query is executed:

ActiveRecord::StatementInvalid: PGError: ERROR:  column wines.region does not exist
LINE 1: SELECT "wines".* FROM "wines"  WHERE "wines"."region" =...

Of course, the table wines has region_id so if I queried for region_id instead I would not get an error.

The question is the following:

Is there a rails-y way to query the Wine object for specific regions using the id in the #where method? I've listed some options below based on what I know I can do.

Option 1: I could change the way that I build the query hash so that each field has _id (like { :region_id => '1234', :varietal_id => '1515' } but not all of the associations from Wine are belongs_to and thus don't have an entry in wines for _id , making the logic more complicated with joins and what not.

Option 2: Build a SQL where clause, again using some logic to determine whether to use the id or join against another table... again the logic would be somewhat more complicated, and delving in to SQL makes it feel less rails-y. Or I could be wrong on that front.

Option(s) 3..n: Things I haven't thought about... your input goes here :)

You could set up a scope in the Wine model to make it more rails-y ...

class Wine < ActiveRecord::Base

  belongs_to :region
  attr_accessible :name, :region_id

  scope :from_region, lambda { |region|
    joins(:region).where(:region_id => region.id)
  }
end

So then you can do something like:

region = Region.find_by_name('France')
wine =   Wine.from_region(region)

Edit 1:

or if you want to be really fancy you could do a scope for multiple regions:

scope :from_regions, lambda { |regions|
  joins(:region).where("region_id in (?)", regions.select(:id))
}

regions = Region.where("name in (?)", ['France','Spain']) # or however you want to select them
wines =   Wine.from_regions(regions) 

Edit 2:

You can also chain scopes and where clauses, if required:

regions = Region.where("name in (?)", ['France','Spain'])
wines =   Wine.from_regions(regions).where(:varietal_id => '1515')

Thanks to all who replied. The answers I got would be great for single condition queries but I needed something that could deal with a varying number of conditions.

I ended up implementing my option #1, which was to build a condition hash by iterating through and concatenating _id to the values:

def query_conditions_hash(conditions)
  conditions.inject({}) do |hash, (k,v)| 
    k = (k.to_s + "_id").to_sym
    hash[k] = v.to_i
    hash
  end
end

So that the method would take a hash that was built from params like this:

{ region => '1235', varietal => '1551', product_attribute => '9' }

and drop an _id onto the end of each key and change the value to an integer:

{ region_id => 1235, varietal_id => 1551, product_attribute_id => 9 }

We'll see how sustainable this is, but this is what I went with for now.

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