0

For example in my Car model i have such fields:

color, price, year

and in form partial i generate form with all this fields. But how to code such logic:

user could enter color and year and i must find with this conditions, user could enter just year or all fields in same time...

And how to write where condition? I could write something like:

if params[:color].present?
car = Car.where(color: params[:color])
end

if params[:color].present? && params[:year].present?
car = Car.where(color: params[:color], year: params[:year])
end
and so over....

But this is very ugly solution, i'm new to rails, and want to know: how is better to solve my problem?

5 Answers 5

4

Check out the has_scope gem: https://github.com/plataformatec/has_scope

It really simplifies a lot of this:

class Graduation < ActiveRecord::Base
  scope :featured, -> { where(:featured => true) }
  scope :by_degree, -> degree { where(:degree => degree) }
  scope :by_period, -> started_at, ended_at { where("started_at = ? AND ended_at = ?", started_at, ended_at) }
end

class GraduationsController < ApplicationController
  has_scope :featured, :type => :boolean
  has_scope :by_degree
  has_scope :by_period, :using => [:started_at, :ended_at], :type => :hash

  def index
    @graduations = apply_scopes(Graduation).all
  end
end

Thats it from the controller side

Sign up to request clarification or add additional context in comments.

4 Comments

how to be if i have models: car and car_photos, and i must check - if car have at least one car_photo - it's set as car has photo ?and my checkbox will be like: scope :by_photo, -> { where(:with_photo => true) }
hello! where did you lost ? )
I think you can have scope :with_photos, -> {joins(:car_photos)} and in your controller: has_scope :with_photos, :type => :boolean
right is : scope :by_with_photo, -> { joins(:car_photos).where('car_photos.car_id is not null') }
2

I would turn those into scopes on your Car model:

scope :by_color, lambda { |color| where(:color => color)}
scope :by_year, lambda { |year| where(:year => year)}

and in your controller you would just conditionally chain them like this:

def index
  @cars = Car.all

  @cars = @cars.by_color(params[:color]) if params[:color].present?
  @cars = @cars.by_year(params[:year]) if params[:year].present?
end

9 Comments

Whoever upvoted - the question refers to multiple params -- populating @cars means only one set of data can be returned
Right, I'm assuming you want the intersection of the data set if multiple params are set, ie all 1989 cars that are red.
It's not my Q, just saying - user could enter color and year and i must find with this conditions, user could enter just year or all fields in same time... - your answer, although really good, only allows one dataset to be returned
@RichPeck upvote is mine. And this code works exactly as OP expects.
@RichPeck You answered yourself. You're not experienced enough to know that the relations are evaluated lazily, only when their value is needed. That's when SQL query is generated. So this piece of code will generate only one SQL query.
|
1
user_params = [:color, :year, :price]

cars = self
user_params.each do |p|
    cars = cars.where(p: params[p]) if params[p].present?
end

1 Comment

also i use rails4; and how to be if i need area, like: price between params[:price1] and params[:price2] how to do like: where params[:price1] <= price <= params[:price2]
0

The typical (naive, but simple) way I would do this is with a generic search method in my model, eg.

class Car < ActiveRecord::Base
  # Just pass params directly in
  def self.search(params)
    # By default we return all cars
    cars = all

    if params[:color].present?
      cars = cars.where(color: params[:color])
    end

    if params[:price1].present? && params[:price2].present?
      cars = cars.where('price between ? and ?', params[:price1], params[:price2])
    end

    # insert more fields here

    cars
  end
end

You can easily keep chaining wheres onto the query like this, and Rails will just AND them all together in the SQL. Then you can just call it with Car.search(params).

6 Comments

This code will return Car class when no param is passed.
which is fine in Rails 4, no?
also i use rails4; and how to be if i need area, like: price between params[:price1] and params[:price2] how to do like: where params[:price1] <= price <= params[:price2]
@sevenseacat no, not really. In Rails 4 is ok to use cars = all instead of cars = self - all method returns Relation.
@MarekLipka ah cool, I knew Rails 4 was a little different in that regard. I'll update my answer.
|
-1

I think you could use params.permit

my_where_params = params.permit(:color, :price, :year).select {|k,v| v.present?}
car = Car.where(my_where_params)

EDIT: I think this only works in rails 4, not sure what version you're using.

EDIT #2 excerpt from site I linked to:

Using permit won't mind if the permitted attribute is missing

params = ActionController::Parameters.new(username: "john", password: "secret")
params.permit(:username, :password, :foobar)
# => { "username"=>"john", "password"=>"secret"}

as you can see, foobar isn't inside the new hash.

EDIT #3 added select block to where_params as it was pointed out in the comments that empty form fields would trigger an empty element to be created in the params hash.

8 Comments

This IMO should be the chosen answer
thanks @RichPeck. All the solutions seem to work fine, I just think this is simplest, glad someone agrees. :)
this won't work fine - if the user doesn't specify a color to search for (for example) it'll only return cars that don't have a color.
@sevenseacat, no it won't, params.permit creates a hash that only has the things you put in it, so if, for example, we only search for price, so the only one of those variables in the params that is present is price, then my_where_params = {price: params[:price]} so that is equivalent to a search for car = Car.where(price: params[:price]) and that will include cars of all colors. See the article I linked to, or try evaluating params.permit in this case to see what happens. Let me know if that makes sense.
The user is submitting a form which will have fields for price, color and year (among other things, guessing). So the fields will always be present, but may not be filled out. If the user doesn't want to search on price, the price field will still be submitted, just empty - this will send the empty price field to the model and completely wreck your searching.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.