Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a list or array of objects in Ruby on Rails

I am trying to make it possible for the users of a web application I am building to create lists of objects that they have created.

For example, the user has a list of objects such as groceries which could be anything from apples to oranges to pop-tarts.

I would then like it to be possible for me to return all of the groceries added to the database by the user and create a list by selecting those that are supposed to be on their grocery list.

Preferably this would be in a style such that they could click check boxes for the ones they wanted and then click save to create a new list.

I have looked into belongs_to, has_many relationships and tried creating a list object which has many groceries, but I can not figure out the form part of this strategy. I would appreciate any/all advice. Thanks!

Here is the code I have currently, I originally omitted it because I do not think I am on the right path, but here it is anyway just in case/provide more context:

Grocery Model:

class Item < ApplicationRecord
    belongs_to :list, optional: true
end

List Model

class List < ApplicationRecord
    has_many :items
end

The List controller

class ListsController < ApplicationController
  before_action :authenticate_user!
  layout 'backend'

  def index
    @lists = List.where(user_id: current_user.id)
  end

  def show
  end

  def new
    @list = List.new
  end

  def edit
  end

  def create
    @list = List.new(list_params)
    @list.user = current_user

    if @list.save
      redirect_to list_path(@list.id), notice: 'List was successfully created.'
    else
      redirect_to list_path(@list.id), notice: 'List was not created.'
    end
  end

  def update
    respond_to do |format|
      if @list.update(list_params)
        format.html { redirect_to @list, notice: 'List was successfully updated.' }
        format.json { render :show, status: :ok, location: @list }
      else
        format.html { render :edit }
        format.json { render json: @list.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @list.destroy
    respond_to do |format|
      format.html { redirect_to lists_url, notice: 'List was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Never trust parameters from the scary internet, only allow the white list through.
    def list_params
      params.require(:list).permit(:name, :items)
    end
end

Not sure what to do about form - was trying something along the lines of http://apidock.com/rails/ActionView/Helpers/FormHelper/check_box

like image 804
Kevin Avatar asked Oct 29 '22 14:10

Kevin


1 Answers

I would solve this by implementing a third model which keeps the association between the groceries and the list. And then you can handle it in forms by using :accepts_nested_attributes_for.

To give an example, here is how I would structure the models:

class List < ApplicationRecord
  has_many :list_items,   inverse_of: :list
  has_many :items,        through: :list_items

  # This allows ListItems to be created at the same time as the List, 
  # but will only create it if the :item_id attribute is present
  accepts_nested_attributes_for :list_items, reject_if: proc { |attr| attr[:item_id].blank? }
end

class Item < ApplicationRecord
  has_many :list_items
  has_many :lists,        through: :list_items
end

class ListItem < ApplicationRecord
  belongs_to :list, inverse_of: :list_items
  belongs_to :item
end

With that model structure in place, here is an example of the view for creating a new List.

<h1>New List</h1>
<%= form_for @list do |f| %>
  <% @items.each_with_index do |item, i| %>
    <%= f.fields_for :list_items, ListItem.new, child_index: i do |list_item_form| %>
      <p>
        <%= list_item_form.check_box :item_id, {}, item.id, "" %> <%= item.name %>
      </p>
    <% end %>
  <% end %>
  <p>
    <%= f.submit 'Create List' %>
  </p>
<% end %>

To explain what is happening here, @items is a preloaded variable to has all the Items that can be added to a list. I loop through each Item and I pass it manually to the FormBuilder method fields_for.

Because I do this manually, I have to specify the :child_index at the same time, otherwise each checkbox would get the same name attribute (i.e. name="list[list_item_attributes][0][item_id]") as the previous item and they would overwrite each others value when being submitted to the server.

And the FormBuilder method check_box has the following declaration:

def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")

So in the above form, I replace those default values so that when a check box is checked, it has the value from item.id and if it is not checked, the value is blank. Combine this with the declaration of accepts_nested_attributes_for in the List model, where we say that it should be rejected if the :item_id is blank, and we get the result of only creating ListItems for the checked Items.

The last thing to make this work, is to permit the nested attributes in the controller, like this:

def allowed_params
  params.require(:list).permit(list_items_attributes: [:item_id])
end
like image 163
DanneManne Avatar answered Nov 15 '22 05:11

DanneManne