Search code examples
ruby-on-railsrubydrop-down-menusimple-form

Rails: Change index based on selected value in dropdown


I have two models:

class Location < ActiveRecord::Base
    has_many :cows
    accepts_nested_attributes_for :cows
end

class Cow < ActiveRecord::Base
    belongs_to :location
end

Every location has an id, a name and three boolean values. A cow consists of an id, two strings and two dates and it also includes a location_id.

In my cow view I have a dropdown of every location. It gets updated automatically whenever I create, edit or delete a location. enter image description here

Below the dropdown you can see all cows ever created. Afer that there is a form to add a new cow with the two string fields and two date fields. enter image description here

Now I want two things: Whenever I select a value in the dropdown, the displayed cows should change. If I select for example location 2, only cows that have a location_id = 2 in the table should be shown. Also when I create a new cow, the id of the selected location should be saved as well (in this example location_id = 2 should be saved in the cow row when I click the button at the very bottom).

This is how the dynamic dropdown looks like in the cow index.html file:

  <%= simple_form_for Cow.new do |f| %>
    <%= f.collection_select :location, Location.order('name').all, :id, :name, { prompt: "Ort auswählen" } %>
  <% end %>

And this is my cow controller:

  def index
    if (params[:location] && Location.all.collect(&:name).include?(params[:location][:name]))
     @cows = Cow.send(params[:location][:name].downcase)
    else
     @cows = Cow.all
    end
  end

  # (...)

  def create
    @cow = Cow.new(cow_params)
    # (...)
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_cow
      @cow = Cow.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def cow_params
      params.require(:cow).permit(:ohrmarke, :hin, :weg, :stallnummer, :location_attributes => [:id])
    end
end

The edited index part just does not work. Perhaps because I have a dropdown for Cow.new and I don't have a submit button. Now the thing is: I need the dropdown (value) for a new cow but also want to display cows that fit to the selected dropdown value. Also I don't want to use a submit button just for the dropdown box. Is there any solution for my problem? I googled so much but just can't find the right answer for my problem.


Edit: I edited my cows controller and my index page: cow controller:

def index
    @locations_all = Location.all
    if (params[:location] && cows = Cow.where(location_id: params[:location]))
      @cows = cows
      @location = Location.where(id: params[:location])
    else
      @cows = Cow.all
    end
end

# (...)

def create
  @cow = Cow.new(cow_params)
  # (...)
end

private
  # Use callbacks to share common setup or constraints between actions.
  def set_cow
    @cow = Cow.find(params[:id])
  end

  # Never trust parameters from the scary internet, only allow the white list through.
  def cow_params
    params.require(:cow).permit(:ohrmarke, :hin, :weg, :stallnummer, :location_attributes => [:id])
  end

cow index.html:

  <%= simple_form_for Cow.new do |f| %>
    <%= f.collection_select :location, Location.order('name').all, :id, :name, { prompt: "Ort auswählen" } %>
    <ul>
      <% @locations_all.each do |loc| %>
        <li><%= link_to loc.name, @cows.index(location: loc.id) %></li>
      <% end %>
    </ul>


    <%= f.hidden_field :location_id, value: @location_id %>

    <table class="striped">
      <thead class="thead-default">
        <tr>
          <th>Ohrmarke</th>
          <th>Stallnr.</th>
          <th>Hin/Weg</th>
        </tr>
      </thead>
      <tbody>
        <!-- ... -->
      </tbody>
    </table><br>

    <%= f.input :ohrmarke, as: :string, input_html: { maxlength: 5 } %>
    <%= f.input :stallnummer, as: :string, input_html: { maxlength: 3 } %>

    <!-- datepicker not functional yet -->
    <input type="date" class="datepicker" id="hin" placeholder="hin">
    <input type="date" class="datepicker" id="hin" placeholder="weg">

    <button class="btn waves-effect waves-light" type="submit" name="action">Zuordnung speichern
      <i class="material-icons right">send</i>
    </button>
  <% end %>

Solution

  • Drop-down & Content

    This is a possible implementation for cows_controller#index.

    def index
        @locations_all = Location.all
        if (params[:location] && cows = Cow.where(location_id: params[:location]))
          @cows = cows
          @location = Location.where(id: params[:location])
        else
          @cows = Cow.all
        end
    end
    

    There are however a few important points to be made and a few problems. The code you're using for the drop-down assumes you're making a POST request which isn't necessary in this case. The same can be achieved with a simple link_to. In order for this to work, you'd have something similar to this:

    <ul>
        <% @locations_all.each do |loc| %>
            <li><%= link_to loc.name, cows_path(location: loc.id) %></li>
        <% end %>
    </ul>
    

    Form

    In order for the form to create a new Cow using the correct location you'd need to include a hidden_field inside it.

    <%= f.hidden_field :location_id, value: @location.id %>
    

    But this introduces a design problem. What happens when you haven't selected any location on the drop-down menu? There are plenty of ways to handle this problem, but my favorite solution is to hide the form in that case.

    <% unless @location %>
        #form-block ...
    <% end %>
    

    This solutions seems ideal if cows can't be created without a specified location.


    Edit

    Ok, now that I can see you HTML. I can tell you what wrong. Your simple_form_for block is surrounding the whole view. Which is probably not what you want. forms are usually POST requests, so you only need those when you plan to send data, or create a new entry on a table. link_to is a GET request which you can request information to the server. This a small example of how my view would look like:

    #drop-down menu
    <div id="cow_menu">
        <ul>
            <% @locations_all.each do |loc| %>
                <li><%= link_to loc.name, cows_path(location: loc.id) %></li>
            <% end %>
        </ul>
    </div>
    
    #table
    <table class="striped">
        # your table logic...  
    </table>
    
    #new Cow form
    <%= simple_form_for Cow.new do |f| %>
        <%= f.hidden_field :location_id, value: @location_id %>
        <%= f.input :ohrmarke, as: :string, input_html: { maxlength: 5 } %>
        <%= f.input :stallnummer, as: :string, input_html: { maxlength: 3 } %>
        <%= f.submit "Zuordnung speichern", class="btn waves-effect waves-light"%>
    <% end %>