Search code examples
ruby-on-railsmodelmany-to-manyentity-relationship

Issues with creating a "many-to-many" relation


I'm trying to do a simple task : I created a "magazine" scaffold, but I want it to have a specific relation : a user can have participate for the creation / redaction of a magazine, and it can take several users to create a magazine.

I checked the APIDock and did the following:

  1. Specified the relation between the magazines and the users

model/magazine.rb

class Magazine < ApplicationRecord
  mount_uploader :thumbnail, ThumbnailUploader
  has_and_belongs_to_many :users
end

model/user.rb

class User < ApplicationRecord
  has_and_belongs_to_many :magazines

  # More code...
end
  1. Created a migration to add a table to link both the models

    class ManyToMany < ActiveRecord::Migration[5.0]
        def change
    
            create_table :magaziness_users, :id => false do |t|
              t.integer :user_id
              t.integer :magazine_id
            end
    
        add_index :magazines_users, [:magazine_id, :user_id]
      end
    end
    

Then I ran the migration

  1. Added the list of all users ever recorded to the database to create a dropdown

    <div class="field">
      <%= f.label :users %>
      <%= f.select :users, User.all_except(current_user).collect {|u| [u.username, u]}, {prompt: 'Add a creator?'}, { :multiple => true, :size => 3 } %>
    </div>
    

But, when I'm saving a new magazine, the user doesn't get saved, and the "magazines_user remains empty.

edit 1

This is an auto-generated controller, since I use the scaffold command to create it. I didn't touch anything excepted the set_magazine function, where I added the Friendly_Id

class MagazinesController < ApplicationController
  before_action :set_magazine, only: [:show, :edit, :update, :destroy]

  def index
    @magazines = magazine.all
  end

  def show
  end

  def new
    @magazine = magazine.new
  end

  def edit
  end

  def create
    @magazine = magazine.new(magazine_params)

    if @magazine.save
      redirect_to @magazine, notice: 'magazine was successfully created.'
    else
      render :new
    end
  end

  def update
    if @magazine.update(magazine_params)
      redirect_to @magazine, notice: 'magazine was successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    @magazine.destroy
    redirect_to magazines_url, notice: 'magazine was successfully destroyed.'
  end

  private
    def set_magazine
      @magazine = magazine.friendly.find(params[:id])
    end

    def magazine_params
      params.require(:magazine).permit(:titre, :description, :apercu, :users)
    end
end

Did I forget any step?


Solution

  • so here is the answer with my code working:

    I did two scaffolds:

    rails generate scaffold user username:string email:uniq password:digest
    rails generate scaffold magazine title:string description:text preview:string
    

    Then added this to magazine migration:

    create_table :magazines_users, id: false do |t|
      t.belongs_to :magazine, index: true
      t.belongs_to :user, index: true
    end
    

    In my form, I added:

    <div class="field">
      <%= f.label :users %>
      <%= f.select :user_ids, User.all.collect { |u| [u.username, u.id] }, {include_blank: true}, {multiple: true} %>
    </div>
    

    And in my magazines controller I only modified magazine_params:

    def magazine_params
      params.require(:magazine).permit(:title, :description, :preview, :user_ids => [])
    end
    

    To see that it works, I added this in magazin show view:

    <p>
      <strong>Users:</strong>
      <%= @magazine.users.map(&:username).join(" - ") %>
    </p>
    

    Of course I added "has_and_belongs_to_many" as you did in User and Magazine models.

    And that's it :) Tested with Rails 5 and it works just fine. :)

    Also I strongly advice you to take a look at the simple_form gem. It has some great methods to handle associations (like has_and_belongs_to_many) easily, like this : <%= f.association :users, collection: User.all_except(current_user).order(:username) %>