Michael Giovanni Pumo
Michael Giovanni Pumo

Reputation: 14784

Rails 4 - Many to many categories won't save to database

I have 3 tables called pins (posts), genres (categories) and genre_pin (many to many between pins and genres because pins can have many genres).

I am trying to build the create form, which will allow the user to select as many genres as they want on a pin and save / create. The create works just fine without error, but it is not saving any relationship records to the genre_pin table. I'm pretty sure I may be missing something very obvious here.

I am using the terms 'pins' and 'genres', but you can think of these as the same as 'posts' and 'categories'.

Pin Model

class Pin < ActiveRecord::Base

    belongs_to :user
    belongs_to :type
    has_many :replies
    has_many :genre_pins
    has_many :genres, :through => :genre_pins

    accepts_nested_attributes_for :genres, allow_destroy: true, reject_if: proc { |genre| genre[:id].blank? }

    validates :user_id, presence: true
    validates :type_id, presence: true
    validates :title, presence: true
    validates :description, presence: true

end

Genre Model

class Genre < ActiveRecord::Base

    has_many :genre_pins
    has_many :pins, :through => :genre_pins

    validates :title, presence: true

end

GenrePin Model

class GenrePin < ActiveRecord::Base

    belongs_to :pin
    belongs_to :genre

    validates_uniqueness_of :pin_id, :scope => :genre_id

end

Next, in my pin#new view, I have the following form:

Pin New View

<%= form_for :pin, url: pins_path do |f| %>

    <div class="small-12 columns">
        <%= f.label :title %>
        <%= f.text_field :title %>
    </div>

    <div class="small-12 columns">
        <%= f.label :description %>
        <%= f.text_area :description, :rows => 10 %>
    </div>

    <div class="small-12 columns">
        <%= f.label :type %>
        <%= f.collection_select :type_id, Type.order(:title), :id, :title, include_blank: false %>
    </div>

    <div class="small-12 columns">
        <%= f.label :genres %>
        <%= f.collection_select :genre_ids, Genre.order(:title), :id, :title, {}, { :multiple => true } %>
    </div>

    <div class="small-12 columns">
        <%= f.submit "Create Pin", :class => "button" %>
    </div>

<% end %>

The view works fine and gets me the available genres from the genres table. However, when I go to save the form, the record (pin) is created, but the relationship with the genres is not. What am I missing here? No errors are thrown, it's just that nothing happens. I am assuming Rails should be creating these PinGenre records through a pins association with genre.

Pin Controller (create)

def create

    @pin = Pin.new(pin_params)
    @pin.user_id = current_user.id
    @pin.save

    if @pin.save
        flash[:success] = "Pin successfully created"
        redirect_to @pin
    else
        flash[:warning] = @pin.errors.full_messages.to_sentence
        redirect_to :action => "new"
    end

end

private

        def pin_params

            params.require(:pin).permit(:title, :description, :type_id, :genre_ids)

        end

Are there further steps I need to make here? Do the records need to be explicitly created by myself?

Thanks for your help and patience. I hope I've made this read clearly. Michael.

Schema.rb

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

  create_table "genre_pins", force: true do |t|
    t.integer "pin_id"
    t.integer "genre_id"
  end

  create_table "genres", force: true do |t|
    t.string "title", null: false
  end

  create_table "pins", force: true do |t|
    t.integer  "user_id",                                          null: false
    t.string   "title",       limit: 160,                          null: false
    t.text     "description",                                      null: false
    t.decimal  "latitude",                precision: 10, scale: 8
    t.decimal  "longitude",               precision: 11, scale: 8
    t.datetime "created_at"
    t.datetime "updated_at"
    t.datetime "deleted_at"
    t.integer  "type_id",                                          null: false
  end

  create_table "profiles", force: true do |t|
    t.integer  "user_id",                                          null: false
    t.string   "first_name",                                       null: false
    t.string   "last_name",                                        null: false
    t.string   "gender"
    t.string   "email"
    t.text     "bio"
    t.decimal  "latitude",                precision: 10, scale: 8
    t.decimal  "longitude",               precision: 11, scale: 8
    t.string   "image",      limit: 2000
    t.datetime "updated_at"
    t.datetime "created_at"
    t.datetime "deleted_at"
  end

  create_table "replies", force: true do |t|
    t.integer  "pin_id",     null: false
    t.integer  "user_id",    null: false
    t.text     "reply",      null: false
    t.datetime "updated_at"
    t.datetime "created_at"
    t.datetime "deleted_at"
  end

  create_table "saves", force: true do |t|
    t.integer  "pin_id",     null: false
    t.integer  "user_id",    null: false
    t.datetime "updated_at"
    t.datetime "created_at"
    t.datetime "deleted_at"
  end

  create_table "settings", force: true do |t|
    t.integer  "user_id",        null: false
    t.boolean  "show_email",     null: false
    t.boolean  "show_telephone", null: false
    t.integer  "timezone",       null: false
    t.datetime "updated_at"
    t.datetime "created_at"
    t.datetime "deleted_at"
  end

  create_table "types", force: true do |t|
    t.string "title", null: false
  end

  create_table "users", force: true do |t|
    t.string   "provider",         limit: 45,   null: false
    t.string   "provider_id",      limit: 45,   null: false
    t.string   "oauth_token",      limit: 1000
    t.datetime "oauth_expires_at"
    t.datetime "updated_at"
    t.datetime "created_at"
    t.datetime "deleted_at"
  end

end

Generated View Source

<form accept-charset="UTF-8" action="/pins" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /><input name="authenticity_token" type="hidden" value="3VO3kVR/62JFf6dJs+yyPTPvYJb3fsUSLRFljSE0Jdk=" /></div>

        <div class="small-12 columns">
            <label for="pin_title">Title</label>
            <input id="pin_title" name="pin[title]" type="text" />
        </div>

        <div class="small-12 columns">
            <label for="pin_description">Description</label>
            <textarea id="pin_description" name="pin[description]" rows="10">
</textarea>
        </div>

        <div class="small-12 columns">
            <label for="pin_type">Type</label>
            <select id="pin_type_id" name="pin[type_id]"><option value="2">Available</option>
<option value="1">Wanted</option></select>
        </div>

        <div class="small-12 columns">
            <label for="pin_genres">Genres</label>
            <input name="pin[genre_ids][]" type="hidden" value="" /><select id="pin_genre_ids" multiple="multiple" name="pin[genre_ids][]"><option value="5">Ambient</option>
<option value="4">Electronic</option>
<option value="2">Indie</option>
<option value="3">Jazz</option>
<option value="6">Metal</option>
<option value="1">Rock</option></select>
        </div>

        <div class="small-12 columns">
            <input class="button" name="commit" type="submit" value="Create Pin" />
        </div>

</form>

Upvotes: 2

Views: 728

Answers (1)

BroiSatse
BroiSatse

Reputation: 44715

You need to tell your controller that 'genre_ids` is expected to be an array:

params.require(:pin).permit(:title, :description, :type_id, genre_ids: [])

However I expect you will get another error (unknown attribute gener_ids). Let me know if that happen.

Upvotes: 3

Related Questions