user2911232
user2911232

Reputation:

Has_many association mapped to different model

I want users to be able to select the languages they speak. I have setup the associations, the table attributes and the part of the form. When I select a language and submit the form I go to the rails console and do a u.languages but I get an empty array back: => []

Here are the logs when I submit the form:

Started POST "/update_user_via_user" for 127.0.0.1 at 2016-03-18 13:26:03 +0200
  ActiveRecord::SchemaMigration Load (0.4ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by UsersController#update_user_via_user as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"CB1Qca0VrBcap9qO6VpKfoi2dG8GNG+tGGNDgCnFEv4E=", "user"=>{ "fullname"=>"John Doe", "languages"=>["", "1", "2"]}, "commit"=>"Save"}
  User Load (28.6ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 3  ORDER BY "users"."id" ASC LIMIT 1
Unpermitted parameters: languages
   (0.1ms)  begin transaction
   (0.1ms)  commit transaction
Redirected to http://127.0.0.1:3000/setup_profile
Completed 302 Found in 163ms (ActiveRecord: 29.5ms)

Now if you look closely on the loogs you will see "Unpermitted parameters: languages".

In my users_controller I have the followings:

  def user_params
    params.require(:user).permit(:languages, :fullname)
  end

and the custom action:

  def update_user_via_user
    if current_user.update(user_params)
      flash.notice = "Your profile was sent for moderation. We will moderate it asap!"
    else
      flash.alert = "Something went wrong! Please try again."
    end
    redirect_to root_path
  end

Some other references: (my question at the end)

schema.rb:

languages table:

  create_table "languages", force: true do |t|
    t.string   "name"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "user_id"
  end

users table:

t.string   "languages"

language.rb model:

class Language < ActiveRecord::Base
  belongs_to :user
end

and user.rb model:

class User < ActiveRecord::Base
  has_many :languages
end

The view:

  <%= f.label :languages %>
  <%= f.select :languages, Language.all.map{ |l| [l.name, "#{l.id}"] }, {}, { :multiple => true } %>

I am not sure why "languages" is not permitted and also if my concept of code is correct

Upvotes: 0

Views: 307

Answers (2)

jvillian
jvillian

Reputation: 20263

I was thinking Deep's answer was the right one, based on this exchange.

I fired up my console and did this:

  irb(main):001:0> x = ActionController::Parameters.new(user: {fullname: "John Doe", languages: ['','1','2']})
  => {"user"=>{"fullname"=>"John Doe", "languages"=>["", "1", "2"]}}

  irb(main):002:0> y = x.require(:user).permit(:fullname, :languages => [])
  => {"fullname"=>"John Doe", "languages"=>["", "1", "2"]}

  irb(main):003:0> y[:languages]
  => ["", "1", "2"]

So, hm.

What's the error message you're getting now? Same as original?

Upvotes: 0

max
max

Reputation: 102036

class Language < ActiveRecord::Base
  belongs_to :user
end

This would setup a one two many relation between users and languages which is not what you want. What you want is a many to many relation:

  • Users can speak many languages
  • Languages have many speakers (users)

So to keep track of this we need a third table:

database diagram

language_users is what is known as a join table. You can actually name the table whatever you want - calling it a + _ + bs is just a convention.

We also need to setup our models to use the join table.

class User < ActiveRecord::Base
  has_many :language_users
  has_many :languages, through: :language_users
end

class Language < ActiveRecord::Base
  has_many :language_users
  has_many :users, through: :language_users
end

# this is a join model that links User & Language
class LanguageUser < ActiveRecord::Base
  belongs_to :user
  belongs_to :language
end

To create a form element where users can select languages you would use:

<%= f.collection_select(:languages_ids,  Language.all, :id, :name, multiple: true) %>

Or:

<%= f.collection_check_boxes(:languages_ids,  Language.all, :id, :name) %>

languages_ids is a special accessor that ActiveRecord creates for has_many associations that lets you set multiple associations at one by passing an array of ids.

Upvotes: 3

Related Questions