Search code examples
ruby-on-railsformsruby-on-rails-4ransack

Form_for each item of a list generated with Ransack


User flow:

  1. User POSTS form indicating which items they have
  2. User sees list of items they have submitted in a column where you have ItemName (the attribute just submitted) and Description (the attribute they were not previously asked for)
  3. On this list of items, I'd now like to give users the chance to PATCH the Description, if they'd like to

The extra thing is that the list of items rendered in the view is sortable by Ransack gem. So the question is, in the snippet of form code below, what variable needs to be called by the update action if @inventories is a variable required by Ransack to make the list sortable in the action 'new'?

@inventories.each do |inventory|
form_for inventory do |f|
f.text_field ...

Right now the form and everything loads correctly, but the description that's submitted doesn't get PATCHed anywhere.

FOR MORE CONTEXT AND FULL CODE:

Controller code:

def new
  #initializes the parent object needed to create the initial inventory of items
  @signup_parent = Signup.find_by_email(session[:signup_email])

  #required by Ransack gem to display results
  @q = @signup_parent.inventories.ransack(params[:q])
  @inventories = @q.result.includes(:signup)
end

def create
  @signup_parent = Signup.find_by_email(session[:signup_email])
  items_to_be_saved = []
  #at some point array is populated with list of items user selects in form
  @signup_parent.inventories.create items_to_be_saved
end

def update
  #since the form_fors are generated by an each do that simultaneously creates forms and generates list of existing items, I figured I'd need to follow the same convention as the 'new' action
  @signup_parent = Signup.find_by_email(session[:signup_email])
  @q = @signup_parent.inventories.ransack(params[:q])
  @inventories = @q.result.includes(:signup)
  redirect_to :action => 'new'
end

View code:

<% if @signup_parent.inventories.count > 0 %>

<div class="table-responsive">
  <table class="table table-condensed table-hover">
    <thead>
      <tr>
        <th><%= sort_link(@q, :item_name, "Item") %></th>
        <th>Description</th>
        <th colspan="5"></th>
      </tr>
    </thead>

    <tbody>
      <% @inventories.each do |inventory| %>
        <tr>
          <td><%= inventory.item_name %></td>
          <td>
            <% if inventory.description.blank? %>
              <%= form_for inventory, url: {action: 'update'}, :html => {:class => "form-inline"} do |f| %> 
                <div class="form-group col-xs-9">
                  <%= f.text_field :description, class: "form-control", placeholder: "Add description" %>
                </div>
                <div class="form-group col-xs-3">
                  <%= f.submit "Update", class: "btn btn-default btn-large btn-block" %>
                </div>
              <% end %>
            <% else %>
              <%= inventory.description %>
            <% end %>
          </td>
          <td>
            <% if inventory.description.present? %>
              <%= button_to %>
              <!-- no clue, separate question (but feel free to solve!) need this button to, 1) ideal solution: "reactivate" form with the description so that users can edit what they've put in, 2) hacky solution: delete the description entirely so that users will have to re-enter it in the form that renders once description is blank again -->
            <% end %>
            <%= button_to 'Remove', inventory, method: :delete, class: "btn btn-default btn-large btn-block" %>
          </td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>

<% else %>

<!-- if they don't have any items, they see the create form -->

<% end %>

Routes

resources :inventories, only: [:new, :create, :destroy]
patch 'inventories', to: 'inventories#update'

Solution

  • In case it's helpful to anyone else:

    Routes code

    resources :inventories, only: [:new, :create, :destroy]
    patch 'inventories/:id/edit', to: 'inventories#update', as: 'edit_inventory'
    patch 'inventories/:id/destroy_description', to: 'inventories#destroy_description', as: 'destroy_description'  
    

    Controller code

    def update
      @inventory = Inventory.find(params[:id])
      @inventory.update_attributes(inventory_update_params)
      if request.referer.include? 'admin'
        redirect_to :action => 'index'
      else
        redirect_to :action => 'new'
      end
    end
    
    def destroy_description
      @inventory = Inventory.find(params[:id])
      @inventory.update_attributes(description: "")
      if request.referer.include? 'admin'
        redirect_to :action => 'index'
      else
        redirect_to :action => 'new'
      end
    end
    

    View code

    <tbody>
      <% @inventories.each do |inventory| %>
        <tr>
          <td><%= inventory.signup.email %></td>
          <td><%= inventory.item_name %></td>
          <td>
            <% if inventory.description.blank? %>
              <%= form_for inventory, url: edit_inventory_path(inventory), method: :patch, :html => {:class => "form-inline", :autocomplete => "off"} do |f| %> 
                <div class="form-group col-xs-10" style="padding:1px;">
                  <%= f.text_field :description, class: "form-control", placeholder: "Add a link or description" %>
                </div>
                <div class="form-group col-xs-2" style="padding:1px;">
                  <%= f.submit "Add", class: "btn btn-default btn-default btn-block" %>
                </div>
              <% end %>
            <% else %>
              <%= inventory.description %>
            <% end %>
          </td>
          <td>
            <% if inventory.description.present? %>
              <div class="col-xs-6" style="padding:1px; ">
                <%= button_to 'New description', destroy_description_path(inventory), method: :patch, class: "btn btn-default btn-default btn-block" %>
              </div>
            <% end %>
              <div class="col-xs-6" style="padding:1px; ">
                <%= button_to 'Remove item', inventory, method: :delete, class: "btn btn-default btn-default btn-block", data: { confirm: 'Once removed, borrowers will no longer be able to see you have this item. To remove an item temporarily (i.e., for a personal trip), please notify us via email' } %>
              </div>
          </td>
        </tr>
      <% end %>
    </tbody>