0

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

  • 2
    Try this: `params.require(:user).permit(:fullname, languages: [])` – Deepesh Mar 18 '16 at 11:44
  • 1
    I am not sure about the concept you are using. The `language` belongs_to `user` and you are allowing the users to choose languages means you need to create new records of languages instead of allowing them to choose in your case. Or else you need a model in between to store it like `user_languages`. It should be on `HABTM` of `has_many through` and to avoid duplicate of languages you should keep a model in between. – Deepesh Mar 18 '16 at 11:49
  • @Deep tried the top one. It gives me a syntax error –  Mar 18 '16 at 12:28

2 Answers2

3
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.

max
  • 96,212
  • 14
  • 104
  • 165
  • There is an additional way to do this with `has_and_belongs_to_many` but that precludes adding any additional data on the join table - so its not as useful in your case. – max Mar 18 '16 at 12:53
  • On a side note - you should not be updating a record via a POST request. You should be using a PATCH request. – max Mar 18 '16 at 13:02
0

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?

Community
  • 1
  • 1
jvillian
  • 19,953
  • 5
  • 31
  • 44