Claudio Claudio - 1 month ago 10
Ruby Question

Ruby on Rails - Devise - Cannot save user with many-to-many associations

I am trying to create a new User with multiple Roles using Ruby on Rails 5.0 and Devise 4.0.

I am using a form and this is what my

sign_up_params
look like:

{
"role_ids"=>["17", "19", "16", "18"],
"email"=>"john@john.com",
"password"=>"123123",
"password_confirmation"=>"123123"
}


This is what my controller looks like:

def create
@user = User.new(sign_up_params)
if @user.save
redirect_to :root, notice: 'Well done'
else
render :new
end
end


The line with
if @user.save
fails.

@user.errors
gives
@messages={:user_roles=>["is invalid"]}
and I have no idea why.

More info:

More digging and I found out that, indeed, my middle table is invalid because it has a
role_id
but no
user_id
because the user hasn't been saved yet. The problem is that I am trying to save everything all at once. Rails should be smart enough to understand that I want to save the user and all the "pending" associations? If not, how can I do? Calling
user.save
doesn't allow me to save the user.

Hope to have been clear enough as to what the problem is. I think that I am doing the wrong things in the controller but I don't know how to do better.. and ideally I would still wanna call
super
in the controller and customise instead of overwriting everything :(




UPDATE




My User model:

class User < ApplicationRecord
devise .......
has_and_belongs_to_many :roles
end


My Role model:

class Role < ApplicationRecord
has_and_belongs_to_many :users
end


My schema:

create table "users" ....
t.stuff ....
....
end

create table "roles" ....
t.stuff ....
....
end

create table "user_roles" ...
t.integer "user_id"
t.integer "role_id"
end

Answer

You can do it in different ways.

Remove the role_ids from the user params and take it separetely,

 eg:
    {
        user:
        {
            "email": "xxx", 
            "password": "xxx"
        },
        role_ids: [1,2,3,4] 
    }  

Method1: @user.roles << @roles

You can use @user.roles << @roles to insert the objects directly.

def create
  @user = User.new(sign_up_params)
  if @user.save
    @roles = Role.where(id: role_ids)

    @user.roles << @roles

    redirect_to :root, notice: 'Well done'
  else
    render :new
  end
end

Method2: @user.roles.create(role_id: role_id)

After the user got created, you can assign roles to that user, using the @user object.

def create
  @user = User.new(sign_up_params)
  if @user.save
    if params[:role_ids].present?
        params[:role_ids].each do |role_id|
            @user.roles.create(role_id: role_id)
        end
    end     
    redirect_to :root, notice: 'Well done'
  else
    render :new
  end
end