简体   繁体   中英

Filtering results in Rails

I am showing a list of questions when the user use the index action. I want to filter this list, showing only rejected questions, questions that only have images attached to them etc.

How do you do that? Do you just add code in the index action that checks if the different named parameters is in the request parameter hash and use them build a query.

myurl.com/questions?status=approved&only_images=yes

Or are there better ways?

You can use has_scope to do this elegantly:

# Model
scope :status, proc {|status| where :status => status}
scope :only_images, ... # query to only include questions with images

# Controller
has_scope :status
has_scope :only_images, :type => boolean

def index
  @questions = apply_scopes(Question).all
end

To keep your controller thin and avoid spaghetti code you can try to use following way:

Controller:

def index
  @questions = Question.filter(params.slice(:status, :only_images, ...) # you still can chain with .order, .paginate, etc
end

Model:

  def self.filter(options)
    options.delete_if { |k, v| v.blank? }
    return self.scoped if options.nil?
    options.inject(self) do |scope, (key, value)|
      return scope if value.blank?
      case key
        when "status" # direct map
          scope.scoped(:conditions => {key => value})
        when "only_images"
          scope.scoped(:conditions => {key => value=="yes" ? true : false})
        #just some examples
        when "some_field_max"
          scope.scoped(:conditions => ["some_field <= ?", value])
        when "some_field_min"
          scope.scoped(:conditions => ["some_field >= ?", value])
        else # unknown key (do nothing. You might also raise an error)
          scope
      end
    end
  end

So, I think there are places where you need to code to be good in such a scenario; the model and the controller.

For the model you should use scopes.

#Model
scope :rejected, lambda { where("accepted = false") }
scope :accepted lambda { where("accepted = true") }
scope :with_image # your query here

In the controller,

def index
  @questions = @questions.send(params[:search])
end

You can send the method name from the UI and directly pass that to the scope in the model. Also, you can avoid an "if" condition for the .all case by passing it from the UI again.

But as this directly exposes Model code to view, you should filter any unwanted filter params that come from the view in a private method in the controller using a before_filter.

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