Filtering index using has_many through relationships Both Cities and Positions are associated to Jobs through a has_many :through relationship.

On the Jobs#Index page, I have a form, which when submitted allows a user to filter jobs based on a combination of city and positions.

I got this to work, but know the code can be a lot better.

This is my Jobs#Index controller:

  def index 
    @cities = City.all
    @positions = Position.all

    @city = City.find(params[:city_id]) if params[:city_id] &amp;&amp; params[:city_id] != "0"
    @position = Position.find(params[:position_id]) if (params[:position_id] &amp;&amp; params[:position_id] != "0")

    @jobs = Job.all
    @jobs = @jobs.includes(:cities).where(cities: { id: @city }) if @city
    @jobs = @jobs.includes(:positions).where(positions: { id: @position }) if @position
    @jobs = @jobs.paginate(page: params[:page], per_page: 5)

  end


This is my Jobs#Index View:

- provide(:title, 'All Jobs')

.thinstripe_career
  .darken
    .container
      .row
        .span10.offset1
          .jobs_header
            .row
              .span7
                h2 All #{@city.name if @city} #{@position.name if @position} Jobs
              .span3
                == link_to "Sign up for Job Alerts!", "#", :class =&gt; "button"

- if current_user &amp;&amp; current_user.admin? 
  == render 'shared/dashboard_header' 

.container
  .row
    .span10.offset1
      .job_search
        == form_tag jobs_path, :method =&gt; 'get', :id =&gt; "jobs_filter" do
          h2 I'd like jobs in
          .filter_sect
            == select_tag :city_id, content_tag(:option,'all cities...', :value=&gt;"0") + options_from_collection_for_select(@cities, "id", "name", params[:city_id].to_i  ), :class =&gt; "basic" 
          .filter_sect
            == select_tag :position_id, content_tag(:option,'all roles...',:value=&gt;"0") + options_from_collection_for_select(@positions, "id", "name", params[:position_id].to_i ), :class =&gt; "basic"           
          .filter_sect.filter_search
            == submit_tag "Search", :class =&gt; "button search_button button_black", :name =&gt; nil

      ul.jobs
        - if @jobs.any?
          == render @jobs
        - else
          li.non_founds
            h2 No jobs founds.
            p There are currently no jobs founds for these combinations. Sign up to requests

      == will_paginate
In models/job.rb, you can add scopes and use them chained on the controller:

class Job &lt; ActiveRecord::Model
  scope :with_cities, -&gt;(city) { includes(:cities).where(cities: { id: @city })  if city }
  scope :with_positions, -&gt;(position) { includes(:positions).where(positions: { id: @position }) if position }

  # rest of the code ...
end




For JobsController, you can:


Add a before_filter to load all the data, and if you need to use that data on other actions, you just need to append the name of that action too.
Use memoization to load the city and the position. 


In controllers/jobs_controller.rb:

class JobsController &lt; ApplicationController
  before_filter :load_data, only: [:index]

  def index
    @jobs = Job.with_cities(city).with_positions(position).paginate(page: params[:page], per_page: 5)
  end

  # ...

  protected:

  def load_data
    @cities = City.all
    @positions = Position.all
  end

  def city
    @city ||= City.find(params[:city_id]) unless params[:city_id].empty?
  end

  def position
    @position ||= Position.find(params[:position_id]) unless params[:position_id].empty?
  end




On the view:

Use try, so instead of:

    - if current_user &amp;&amp; current_user.admin? 
      == render 'shared/dashboard_header' 


you could write:

    == render 'shared/dashboard_header' if current_user.try(:admin?)


Use include_blank and prompt options in select_tag. 
This not only simplifies the views, but also allows to use params[city_id].empty? on the controller.

select_tag :city_id, 
  options_from_collection_for_select(@cities, :id, :name, params[:city_id].to_i), 
    include_blank: true, prompt: "all cities...", class: "basic" 


Finally, you could extract ul.jobs into a partial:

= render partial: 'jobs', locals: { jobs: @jobs }


Or even the .job_search div, especially if you'll be needing this somewhere else.