Since the work of extracting multiple data from the select box in Ruby on Rails is unexpectedly complicated, the process of trial and error so far is organized here. It has not been resolved yet, so we will update it as it progresses.
ruby 2.6.4p104 RubyGems 3.0.3 Rails 5.2.3
Currently, I am making a language learning SNS site. This time, on the profile edit screen, I would like to create a select box that allows the user to select multiple languages that they are learning. Eventually, I'd like to use a gem that covers all languages around the world, but first of all, as a test stage, "Japanese, English, Chinese, We will make a select box containing 4 languages of "Spanish".
I think that "user" and "language" have a many-to-many relationship, so I used an intermediate table called "user_language" to connect "user" and "language" in a many-to-many relationship.
** User model **
models/user.rb
class User < ApplicationRecord
has_secure_password
has_many :languages, through: :user_language
has_many :user_language
def language_name
::LanguageSelect::LANGUAGES[language]
end
end
** Language model **
models/language.rb
class Language < ApplicationRecord
has_many :users, through: :user_language
has_many :user_language
end
** Intermediate table between User model and Language model **
models/user_language.rb
class Language < ApplicationRecord
belongs_to :user
belongs_to :language
end
schema
db/schema.rb
ActiveRecord::Schema.define(version: 2020_05_21_034306) do
create_table "languages", force: :cascade do |t|
t.string "description"
t.boolean "done"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_languages_on_user_id"
end
create_table "user_languages", force: :cascade do |t|
t.integer "user_id"
t.integer "language_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["language_id"], name: "index_user_languages_on_language_id"
t.index ["user_id"], name: "index_user_languages_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_name"
t.string "password_digest"
t.string "cover_image_name"
t.string "sex"
t.string "country"
t.string "language"
t.text "introduction"
end
The profile edit page is described as follows.
views/users/edit.html.erb
<div class="col-1">Spoken language:</div>
<div class="col-2">
<select name="language[]" multiple="multiple" class="form-control">
<option>Japanese</option>
<option>English</option>
<option>Chinese</option>
<option>Spanish</option>
<input type="button" value="+" class="add pluralBtn">
<input type="button" value="-" class="del pluralBtn">
</div>
The controller is described as follows.
controllers/users_controller.rb
def update
@user = User.find_by(id: params[:id])
@user.languages = params[:language]
if @user.save
flash[:notice] = "Edited user information"
redirect_to("/users/#{@user.id}")
else
render("users/edit")
end
end
Finally, the profile page is described as follows.
views/users/show.html.erb
<div class="language">
<% @user.languages.each do |language| %>
<%= @user.language %>
<% end %>
</div>
Now that I'm ready, when I try to select and save multiple languages, I get the following error:
ActiveRecord::HasManyThroughOrderError in UsersController#update
Cannot have a has_many :through association 'User#languages' which goes through 'User#user_language' before the through association is defined.
Extracted source (around line #125):
@user.languages = params[:language]
When describing a many-to-many relationship, the above error seems to occur unless the code related to the intermediate table (user_language in this case) is written first. With reference to this article, I rewrote the relation as follows.
** User model **
models/user.rb
class User < ApplicationRecord
has_secure_password
has_many :user_language
has_many :languages, through: :user_language
def language_name
::LanguageSelect::LANGUAGES[language]
end
end
** Language model **
models/language.rb
class Language < ApplicationRecord
has_many :user_language
has_many :users, through: :user_language
end
By rewriting, the HasManyThroughOrder error disappeared, but this time the following error was displayed.
ActiveRecord::AssociationTypeMismatch in UsersController#update
Language(#135869460) expected, got "English" which is an instance of String(#21954020)
@user.languages = params[:language]
It seems that I intended to receive an instance of Language but received the string "English". So, to prevent that, I decided to add a value attribute to each of the selected languages on the profile edit page.
views/users/edit.html.erb
<div class="col-1">Spoken language:</div>
<div class="col-2">
<select name="language[]" multiple="multiple" class="form-control">
<option>Japanese</option>
<option>English</option>
<option>Chinese</option>
<option>Spanish</option>
<input type="button" value="+" class="add pluralBtn">
<input type="button" value="-" class="del pluralBtn">
</div>
The thing that was like this was changed as follows.
views/users/edit.html.erb
<div class="col-1">Spoken language:</div>
<div class="col-2">
<select name="language[]" multiple="multiple" class="form-control">
<option value="1">Japanese</option>
<option value="2">English</option>
<option value="3">Chinese</option>
<option value="4">Spanish</option>
<input type="button" value="+" class="add pluralBtn">
<input type="button" value="-" class="del pluralBtn">
</div>
I thought that this solved it, but the same error statement again.
ActiveRecord::AssociationTypeMismatch in UsersController#update
Language(#128573540) expected, got "2" which is an instance of String(#21954020)
@user.languages = params[:language]
I intended to pass language_id
this time, but it seems that it is also passed as a" string "called"2"
with double quotes.
Actually, this is probably due to the fact that Data that can be sent with Rails value is limited to strings. was.
If things don't go well, I may want to use radio buttons, etc., but in the end I want to select more than 100 languages, so I will stick to the select box and do my best.
For the time being, I'm stumbling here. If you organize it in detail, if you define the information of the language that the user is learning in the controller as "@ user.languages" as follows, the error of ʻAssociationTypeMismatch` will be displayed.
controllers/users_controller.rb
def update
@user = User.find_by(id: params[:id])
@user.languages = params[:language]
if @user.save
flash[:notice] = "Edited user information"
redirect_to("/users/#{@user.id}")
else
render("users/edit")
end
end
Also, if you define "@ user.language" in the singular form as shown below, no error will be displayed, but it will be output as an array (as a character string) like [" 1 "," 2 "]
. Will be done.
controllers/users_controller.rb
def update
@user = User.find_by(id: params[:id])
@user.language = params[:language]
if @user.save
flash[:notice] = "Edited user information"
redirect_to("/users/#{@user.id}")
else
render("users/edit")
end
end
Like Japanese Chinese
, we will consider a solution so that the data can be individually extracted and displayed without including [] and" ".
There are two ways I'm thinking about:
** Method 1 **
After receiving the character string [" 1 "," 2 "]
, it is converted to a numeric type.
** Method 2 ** If you can specify the numeric type before receiving the data, specify it.
I don't know how to describe both of them, and I'm currently in a situation of trial and error. I'm sorry for those who read it in anticipation of a solution, but I will update it as it progresses.