Carl
Carl

Reputation: 991

Add model/database association upon create

I have a model named Entry, which has many Categories. The page where I create/edit the Entry has checkboxes for every Category.

When I am editing an Entry, everything works okay. When I create the Entry, I get as an error for the @entry:

:entry_categories=>["is invalid"]

My thinking is that rails can't create the entry_categories because it doesn't know the id of the Entry ( which it shouldn't, it hasn't been assigned an id yet ).

I feel like this is a very common thing to try to do. I haven't been able to find an answer though. Here comes the code spamming, there must be something I am missing and hopefully some more experienced eyes can see it.

entry.rb

class Entry < ActiveRecord::Base

  validates_presence_of :contents
  validates_presence_of :title

  has_many :entry_categories, dependent: :destroy
  has_many :categories, through: :entry_categories

  belongs_to :book
  validates_presence_of :book

end

entry_category.rb

class EntryCategory < ActiveRecord::Base

  belongs_to :entry
  belongs_to :category

  validates_presence_of :entry
  validates_presence_of :category

end

category.rb

class Category < ActiveRecord::Base
  validates_presence_of :name
  validates_uniqueness_of :name

  has_many :entry_categories, dependent: :destroy
  has_many :entries, through: :entry_categories

end

entries_controller.rb

class EntriesController < ApplicationController
    before_action :find_entry, only: [ :show, :edit, :update, :destroy ]
    before_action :find_book, only: [ :new, :create, :index ]
    before_action :authenticate_admin!, only: [:new, :create, :edit, :update, :destroy ]

    def new
        @entry = Entry.new
    end

    def create
        @entry  = @book.entries.new( entry_params )
        if @entry.save
            redirect_to entry_path( @entry ), notice: 'Entry Created'
        else
            render :new
        end
    end

    def show
        @categories = Category.joins( :entry_categories ).where( "entry_categories.entry_id = #{@entry.id} " ).select( "name, categories.id " )
        @category_class = @categories.first.name.downcase.gsub( / /, '_' ) if @categories.any?
    end

    def index
        @entries = @book ? @book.entries : Entry.all
    end

    def edit
    end

    def update
        if @entry.update( entry_params )
            redirect_to entry_path( @entry ), notice: 'Entry Updated'
        else
            render :edit
        end
    end

    def destroy
        @book = @entry.book
        @entry.destroy
        redirect_to book_path( @book ) , notice: 'Entry Destroyed'
    end

    protected
    def entry_params
        params.require(:entry).permit( :title, :contents, :year, :month, :day, category_ids: [] )
    end

    def find_entry
        @entry = Entry.find( params[:id] )
    end

    def find_book
        @book = Book.find( params[ :book_id ] )
    rescue
        @book = nil
    end

end

_form.html.erb

<%= form_for [ @book, @entry ] do | form | %>
    <%= content_tag :p, title %>
    <%= form.text_field :title, placeholder: 'Title', required: true %>
    <%= form.number_field :year, placeholder: 'Year' %>
    <%= form.number_field :month, placeholder: 'Month' %>
    <%= form.number_field :day, placeholder: 'Day'     %>
    <%= form.text_area :contents %>
    <fieldset>
    <legend>Categories</legend>
    <%= form.collection_check_boxes(:category_ids, Category.all, :id, :name ) %>
    </fieldset>
    <%= form.submit %>
<% end %>

So again, the entry_categories are invalid in the create method, in the update they are fine. It's the same html file.

There must be some way to tell rails to save the Entry before trying to save the EntryCategory?

Thanks.

Upvotes: 1

Views: 63

Answers (3)

Carl
Carl

Reputation: 991

I managed to get this working by taking the validations out of EntryCategory:

class EntryCategory < ActiveRecord::Base

  belongs_to :entry
  belongs_to :category

  validates_presence_of :category

end

I'm not particularity happy about this solution, and would still appreciate other thoughts.

Upvotes: 1

Wally Ali
Wally Ali

Reputation: 2508

Try this:

replace this code:

<%= form.collection_check_boxes(:category_ids, Category.all, :id, :name ) %>

with:

<% Category.all.order(name: :asc).each do |category| %>
 <div>
    <%= check_box_tag "entry[category_ids][]", category.id %>
    <%= category.name %>
</div>

you can format it with fieldset instead of div

Upvotes: 0

Saurabh
Saurabh

Reputation: 73589

I think you can use any of the following approach:

You can use autosave functionality of Active Record association. By this, when you will save EntryCategory, it will automatically save Entry as well.

class EntryCategory < ActiveRecord::Base

  belongs_to :entry , autosave: true
  #rest of the code
end

You can also use before_save callback of active record. by this, whenever you will save EntryCategory, it will first call a specified method, than proceed with saving.

class EntryCategory < ActiveRecord::Base

  before_save :save_associated_entries
  #rest of the code

  def save_associated_entries
    # code to save associated entries here
  end 
end

Upvotes: 0

Related Questions