Until you retrieve multiple data from the select box

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.

version information

ruby 2.6.4p104 RubyGems 3.0.3 Rails 5.2.3

Thing you want to do

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

Many-to-many relationship

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>

HasManyThroughOrder error

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

AssociationType Missmatch error

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>

AssociationTypeMissmatch error again

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.

from now on

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.

Recommended Posts

Until you retrieve multiple data from the select box
Until you run the Apache Velocity sample code
Let's use the pull-down (select box) -Select the posting destination-