shou2017.com
JP

Rails Register other models together when registering

Fri Sep 8, 2017
Sat Aug 10, 2024

Objective

When creating an article, I want to create categories at the same time. The article is the parent, and the categories are the children.

I am using Rails 5. This is a memo for adding categories to an already created article controller and model.

Register other models together in Rails

Creating the Model

Create the category model.

$ rails g model category

Setting up the association in article.rb

class Article < ApplicationRecord
  # Use accepts_nested_attributes_for
  has_many :categories, dependent: :destroy
  accepts_nested_attributes_for :categories, allow_destroy: true
end

Setting up the association in category.rb

In Rails 5, don’t forget to set optional: true, otherwise it won’t work!

class Category < ApplicationRecord
  # Use accepts_nested_attributes_for
  belongs_to :article, optional: true
end

schema.rb

Add article_id to the categories table.

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

Setting up the Controller

articles_controller.rb

When creating a new article, use build to prepare the associated categories. When the parent is saved, the children are automatically saved as well.

# GET /articles/new
def new
  @article = Article.new
  @article.categories.build
  # To create two categories, use:
  # 2.times { @article.categories.build }
end

Setting up params

def article_params
  params.require(:article).permit(
    :title,
    :content,
    :editer,
    categories_attributes: [:id, :name]
  )
end

Full Controller Example

class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]

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

  # GET /articles/1
  def show
  end

  # GET /articles/new
  def new
    @article = Article.new
    @article.categories.build
  end

  # GET /articles/1/edit
  def edit
  end

  # POST /articles
  def create
    @article = Article.new(article_params)

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

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

  # DELETE /articles/1
  def destroy
    @article.destroy
    respond_to do |format|
      format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def article_params
    params.require(:article).permit(
      :title,
      :content,
      :editer,
      categories_attributes: [:id, :name]
    )
  end
end

Setting up the Views

Form View

<div class="field">
  <%= f.label :Category %>
  <%= f.fields_for :categories do |category| %>
    <%= category.text_field :name %>
  <% end  %>
</div>

Full Form Example

<%= form_for(article) do |f| %>
  <% if article.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>
      <ul>
        <% article.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end  %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :Title %>
    <%= f.text_field :title %>
  </div>

  <div class="field">
    <%= f.label :Content %>
    <%= f.text_field :content %>
  </div>

  <div class="field">
    <%= f.label :Category %>
    <%= f.fields_for :categories do |category| %>
      <%= category.text_field :name %>
    <% end  %>
  </div>

  <div class="field">
    <%= f.label :Editor %>
    <%= f.text_field :editer %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end  %>

Show View

<p>
  <strong>Category:</strong>
  <% @article.categories.each do |category| %>
    <td><%= category.name %></td>
  <% end  %>
</p>

That’s it!

See Also