Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pre-populate collection_check_boxes in an edit form?

GitHub repo: https://github.com/Yorkshireman/mywordlist

I've googled the hell out of this one. I'm sure there's a way, possibly requiring some code inside a html options hash, but I can't work it out. Any ideas?

When visiting the _edit_word_form.html.erb partial for a Word that DOES have one or more categories, the Categories checkboxes are all unchecked, requiring the user to select them again, even if they don't want to change the Categories.

The text fields for the :title and :description ARE pre-populated (thankfully).

_edit_word_form.html.erb:

<%= form_for(@word) do %>

  <%= fields_for :word, @word do |word_form| %>
    <div class="field form-group">
      <%= word_form.label(:title, "Word:") %><br>
      <%= word_form.text_field(:title, id: "new_word", required: true, autofocus: true, class: "form-control") %>
    </div>

    <div class="field form-group">
      <%= word_form.label(:description, "Definition:") %><br>
      <%= word_form.text_area(:description, class: "form-control") %>
    </div>
  <% end %>


  <%= fields_for :category, @category do |category_form| %>
    <% if current_user.word_list.categories.count > 0 %>
      <div class="field form-group">
        <%= category_form.label(:title, "Choose from existing Categories:") %><br>
        <%= category_form.collection_check_boxes(:category_ids, current_user.word_list.categories.all, :id, :title) do |b| %>
          <%= b.label(class: "checkbox-inline") { b.check_box + b.text } %>
        <% end %>
      </div>
    <% end %>

    <h4>AND/OR...</h4>

    <div class="field form-group">
      <%= category_form.label(:title, "Create and Use a New Category:") %><br>
      <%= category_form.text_field(:title, class: "form-control") %>
    </div>
  <% end %>

  <div class="actions">
    <%= submit_tag("Update!", class: "btn btn-block btn-primary btn-lg") %>
  </div>
<% end %>

Relevant part of the words/index.html.erb:

<% current_user.word_list.words.alphabetical_order_asc.each do |word| %>
              <tr>
                <td>
                  <%= link_to edit_word_path(word) do %>
                    <%= word.title %>
                    <span class="glyphicon glyphicon-pencil"></span>
                  <% end %>
                </td>
                <td><%= word.description %></td>
                <td>
                  <% word.categories.alphabetical_order_asc.each do |category| %>
                    <a class="btn btn-info btn-sm", role="button">
                      <%= category.title %>
                    </a>
                  <% end %>
                </td>
                <td>
                  <%= link_to word, method: :delete, data: { confirm: 'Are you sure?' } do %>
                    <span class="glyphicon glyphicon-remove"></span>
                  <% end %>
                </td>
              </tr>
            <% end %>

words_controller.rb:

class WordsController < ApplicationController
  before_action :set_word, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!

  # GET /words
  # GET /words.json
  def index
    @words = Word.all
    @quotes = Quote.all
  end

  # GET /words/1
  # GET /words/1.json
  def show
  end

  # GET /words/new
  def new
    @word = current_user.word_list.words.build
  end

  # GET /words/1/edit
  def edit
  end

  # POST /words
  # POST /words.json
  def create
    @word = Word.new(word_params)

    respond_to do |format|
      if @word.save
        format.html { redirect_to @word, notice: 'Word was successfully created.' }
        format.json { render :show, status: :created, location: @word }
      else
        format.html { render :new }
        format.json { render json: @word.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /words/1
  # PATCH/PUT /words/1.json
  def update
    #need to first remove categories from the word
    @word.categories.each do |category|
      @word.categories.delete category
    end

    #then push categories in from the category_params
    if params["category"].include?(:category_ids)
      (params["category"])["category_ids"].each do |i|
        next if i.to_i == 0
        @word.categories << Category.find(i.to_i) unless @word.categories.include?(Category.find(i.to_i))
      end
    end

    if category_params.include?(:title) && ((params["category"])["title"]) != ""
      @word.categories << current_user.word_list.categories.build(title: (params["category"])["title"])
    end

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

  # DELETE /words/1
  # DELETE /words/1.json
  def destroy
    @word.destroy
    respond_to do |format|
      format.html { redirect_to words_url, notice: 'Word was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

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

    # Never trust parameters from the scary internet, only allow the white list through.
    def word_params
      params.require(:word).permit(:title, :description, :category_ids)
    end

    def category_params
      params.require(:category).permit(:title, :category_ids, :category_id)
    end
end

categories_controller.rb:

class CategoriesController < ApplicationController
  before_action :set_category, only: [:show, :edit, :update, :destroy]

  # GET /categories
  # GET /categories.json
  def index
    @categories = Category.all
  end

  # GET /categories/1
  # GET /categories/1.json
  def show
  end

  # GET /categories/new
  def new
    @category = current_user.word_list.categories.build
  end

  # GET /categories/1/edit
  def edit
  end

  # POST /categories
  # POST /categories.json
  def create
    @category = Category.new(category_params)

    respond_to do |format|
      if @category.save
        format.html { redirect_to @category, notice: 'Category was successfully created.' }
        format.json { render :show, status: :created, location: @category }
      else
        format.html { render :new }
        format.json { render json: @category.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /categories/1
  # PATCH/PUT /categories/1.json
  def update
    respond_to do |format|
      if @category.update(category_params)
        format.html { redirect_to @category, notice: 'Category was successfully updated.' }
        format.json { render :show, status: :ok, location: @category }
      else
        format.html { render :edit }
        format.json { render json: @category.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /categories/1
  # DELETE /categories/1.json
  def destroy
    @category.destroy
    respond_to do |format|
      format.html { redirect_to categories_url, notice: 'Category was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

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

    # Never trust parameters from the scary internet, only allow the white list through.
    def category_params
      params.require(:category).permit(:title, :word_list_id, :category_ids)
    end
end

word_lists_controller.rb:

class WordListsController < ApplicationController
  before_action :set_word_list, only: [:show, :edit, :update, :destroy]


  def from_category
    @selected = current_user.word_list.words.joins(:categories).where( categories: {id: (params[:category_id])} )
    respond_to do |format|
      format.js
    end
  end

  def all_words
    respond_to do |format|
      format.js
    end
  end

  # GET /word_lists
  # GET /word_lists.json
  def index
    @word_lists = WordList.all
  end

  # GET /word_lists/1
  # GET /word_lists/1.json
  def show
    @words = Word.all
    @word_list = WordList.find(params[:id])
  end

  # GET /word_lists/new
  def new
    @word_list = WordList.new
  end

  # GET /word_lists/1/edit
  def edit
  end

  # POST /word_lists
  # POST /word_lists.json
  def create
    @word_list = WordList.new(word_list_params)

    respond_to do |format|
      if @word_list.save
        format.html { redirect_to @word_list, notice: 'Word list was successfully created.' }
        format.json { render :show, status: :created, location: @word_list }
      else
        format.html { render :new }
        format.json { render json: @word_list.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /word_lists/1
  # PATCH/PUT /word_lists/1.json
  def update
    respond_to do |format|
      if @word_list.update(word_list_params)
        format.html { redirect_to @word_list, notice: 'Word list was successfully updated.' }
        format.json { render :show, status: :ok, location: @word_list }
      else
        format.html { render :edit }
        format.json { render json: @word_list.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /word_lists/1
  # DELETE /word_lists/1.json
  def destroy
    @word_list.destroy
    respond_to do |format|
      format.html { redirect_to word_lists_url, notice: 'Word list was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

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

    # Never trust parameters from the scary internet, only allow the white list through.
    def word_list_params
      params[:word_list]
    end
end

word_list.rb:

class WordList < ActiveRecord::Base

    belongs_to :user

    has_many :words

    has_many :categories

end

word.rb:

class Word < ActiveRecord::Base

  belongs_to :word_list

  has_and_belongs_to_many :categories

  validates :title, presence: true

  scope :alphabetical_order_asc, -> { order("title ASC") }

end

category.rb:

class Category < ActiveRecord::Base

    has_and_belongs_to_many :words

    belongs_to :word_list

    validates :title, presence: true

    scope :alphabetical_order_asc, -> { order("title ASC") }

end

schema.rb:

ActiveRecord::Schema.define(version: 20150609234013) do

  create_table "categories", force: :cascade do |t|
    t.string   "title"
    t.datetime "created_at",   null: false
    t.datetime "updated_at",   null: false
    t.integer  "word_list_id"
  end

  add_index "categories", ["word_list_id"], name: "index_categories_on_word_list_id"

  create_table "categories_words", id: false, force: :cascade do |t|
    t.integer "category_id"
    t.integer "word_id"
  end

  add_index "categories_words", ["category_id"], name: "index_categories_words_on_category_id"
  add_index "categories_words", ["word_id"], name: "index_categories_words_on_word_id"

  create_table "quotes", force: :cascade do |t|
    t.text     "content"
    t.string   "author"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.string   "email",                  default: "", null: false
    t.string   "encrypted_password",     default: "", null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,  null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string   "current_sign_in_ip"
    t.string   "last_sign_in_ip"
    t.datetime "created_at",                          null: false
    t.datetime "updated_at",                          null: false
  end

  add_index "users", ["email"], name: "index_users_on_email", unique: true
  add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

  create_table "word_lists", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer  "user_id"
  end

  create_table "words", force: :cascade do |t|
    t.string   "title"
    t.text     "description"
    t.datetime "created_at",   null: false
    t.datetime "updated_at",   null: false
    t.integer  "word_list_id"
  end

  add_index "words", ["word_list_id"], name: "index_words_on_word_list_id"

end

routes.rb:

Rails.application.routes.draw do
  resources :quotes
  resources :categories
  resources :words
  devise_for :users, controllers: { registrations: "users/registrations" }

  # The priority is based upon order of creation: first created -> highest priority.
  # See how all your routes lay out with "rake routes".

  # You can have the root of your site routed with "root"
  # root 'welcome#index'
  root 'pages#home'

  post 'create_word_and_category' => 'new_word#create_word_and_category'
end
like image 388
Yorkshireman Avatar asked Jun 10 '15 20:06

Yorkshireman


4 Answers

The discussion might not be active anymore but I'll share my answer for the future visitors.

Adding "{ checked: @array.map(&:to_param) }" option as the last argument of collection_check_boxes may resolve your problem. Refer this link.

Example:
Suppose you have Software model and Platform(OS) model and want to choose one or more OS that support your software.

#views/softwares/edit.html.erb

<%= form_for @software do |f| %>
  ...
  <%= f.label :supported_platform %>
  <%= f.collection_check_boxes(:platform_ids, @platforms, :id, :platform_name, { checked: @software.platform_ids.map(&:to_param) }) %>
  ...
  <%= f.submit "Save", class: 'btn btn-success' %>
<% end %>

note:
@software.platform_ids should be an array. If you are using SQLite, you have to convert string into array when you pull the data out. I tested it with SQLite and confirmed working as well. See my post for more detail.

like image 111
Harold Asai Avatar answered Oct 18 '22 18:10

Harold Asai


This should be closer to what you were looking for:

<%= b.label(class: "checkbox-inline", :"data-value" => b.value) { b.check_box + b.text } %>
like image 39
WhyEnBe Avatar answered Oct 18 '22 17:10

WhyEnBe


Try changing your _edit_word_form.html.erb like this

<%= form_for(@word) do |f| %>

    <div class="field form-group">
      <%= f.label(:title, "Word:") %><br>
      <%= f.text_field(:title, id: "new_word", required: true, autofocus: true, class: "form-control") %>
    </div>

    <div class="field form-group">
      <%= f.label(:description, "Definition:") %><br>
      <%= f.text_area(:description, class: "form-control") %>
    </div>

  <%= f.fields_for :category do |category_form| %>
    <% if current_user.word_list.categories.count > 0 %>
      <div class="field form-group">
        <%= category_form.label(:title, "Choose from existing Categories:") %><br>
        <%= category_form.collection_check_boxes(:category_ids, current_user.word_list.categories.all, :id, :title) do |b| %>
          <%= b.label(class: "checkbox-inline") { b.check_box + b.text } %>
        <% end %>
      </div>
    <% end %>

    <h4>AND/OR...</h4>

    <div class="field form-group">
      <%= category_form.label(:title, "Create and Use a New Category:") %><br>
      <%= category_form.text_field(:title, class: "form-control") %>
    </div>
  <% end %>

  <div class="actions">
    <%= f.submit("Update!", class: "btn btn-block btn-primary btn-lg") %>
  </div>
<% end %>
like image 1
Pavan Avatar answered Oct 18 '22 18:10

Pavan


So user is editing a word from their word list, but you want to show checkboxes for all categories for all the words in their word list, checking those categories attached to the word being editing. Is that correct?

It looks like you're missing out the first parameter in #collection_check_boxes, which should be the object you call :category_ids on.

If the object is the user's word_list, then something like:

<%= category_form.collection_check_boxes(current_user.word_list, :category_ids, current_user.word_list.categories.all, :id, :title) do |b| %>

May not be the exact answer--I can't test it--but hope it gives you something to go on.

like image 1
Dan Barron Avatar answered Oct 18 '22 16:10

Dan Barron