LoginSignup
6
5

More than 3 years have passed since last update.

accepts_nested_attributes_forとf.collection_check_boxesを使わずに、一つのフォームでモデルと中間テーブルを同時に保存する 

Last updated at Posted at 2019-10-20

多対多のリレーションを考えたときに、親モデルを作成すると同時に子モデルも作成したいときがあります。ブログにカテゴリーをつけたいときとか。
accepts_nested_attributes_forを使用したやり方がよく紹介されていますが、評判が良くないといった書き込みがあったり、個人的に挙動がわかりにくかったのでaccepts_nested_attributes_forを使用しない方法を記載しました。
ターゲットはあくまでも初心者ですので、ていねいにコメントを多めにしながら書きました。

サンプルとしてuser(ユーザー)とjob(仕事)の多対多の関係を考えます

userモデルとjobモデルが中間テーブルuser_jobで多対多のリレーションを持つとします。

とりあえずuserを作成します。

userをつくりましょう。まずモデルから

$rails g model user name:string
$rails db:migrate

今回は名前だけ持たせます。
コントローラをつくります。userをつくるだけのシンプルなものです。

$rails g controller users
users_controller
def new
    @user = User.new
    @users = User.all
  end

  def create
    User.create(user_params)
    redirect_to new_user_path
  end

  private
  def user_params
    params.require(:user).permit(:name)
  end

users/new.html.erb
<h1>ユーザー登録</h1>
<%= form_for @user do |f| %>
<%= f.label :name %>
<%= f.text_field :name%>
<%= f.submit "登録" %>
<% end %>

<% @users.each do |user| %>
<%= "#{user.id}:#{user.name}" %><br>
<% end %>
config/routes
resources :users

スクリーンショット 2019-10-18 11.34.29.png
こんな感じになりましたでしょうか。
この段階でhttp://www.localhost:3000/users/newにアクセスすればuserの名前を登録するかんたんなwebサイトになります。一郎、次郎、三郎を作成しました。

user登録と同時に中間テーブル(user_jobs)を作成することを考えてみる

とりあえずjobモデルをつくりましょう

名前だけを持たせます。

$rails g model Job name:string
$rails db: migrate

migrateを忘れずに。
jobをいくつかつくりたいのですが、formを作るのが面倒なのでコンソールでjobを作成します。

> Job.create name: "farmer"
> Job.create name: "fisherman"

今回はfarmerとfishermanを作成しました。

中間テーブルuser_jobsを作成する

$rails g model UserJob user:references job:references
$rails db:migrate

user:references job:referencesとすることでuser_idとjob_idを自動生成してくれます。

has_manyとbelongs_toを加える

user.rb
#これを書き加える
has_many :user_jobs
has_many :jobs, through: :user_jobs

has_manyを書き加えることでuser.jobs.builduser.user_jobsといったメソッドが使えるようになる。belongs_toは中間テーブル作成時に初めからあるはずです。

ここでユーザー登録と同時にuser_jobsも登録することを考えます。

users/new.html.erb
<h1>ユーザー登録</h1>
<%= form_for @user do |f| %>
  <!--ここはいつも通りの@userのname部分のform-->
  <%= f.label :name %>
  <%= f.text_field :name%>
  <!--ここが中間テーブルuser_jobsを保存させるためのform
  パラメータを扱いやすくするためfields_forでネストさせます-->
  <%= f.fields_for :job do |j| %>
    <!--jobは複数あるのでeachで全て取り出してチェックボックスにします-->
    <% Job.all.each do |job| %>
      <%= j.label job.name %>
      <%= j.check_box job.name,{}, true, false %>
    <% end %>
  <% end %>
  <%= f.submit "登録" %>
<% end %>
<!--この部分は保存したuserとjobを確認しているだけなので分かれば何でもいいです。-->
<% @users.each do |user| %>
  <% if user.jobs.first.nil? %>
    <%= "#{user.id}:#{user.name}" %><br>
  <% else %>
    <%= "#{user.id}:#{user.name}:" %>
    <% user.jobs.each do |job| %>
      <%= "#{job.name}" %>
    <% end %>
    <br>
  <% end %>
<% end %>

f.fields_forをつかって中間テーブル作成用のformをuser作成formにネストしています。
ここでは、あらかじめ登録したjobをチェックボックスで選択してuserに登録するようにします。
formでネストすることによって
params[:user][:job]= {"farmer"=>"true", "fisherman"=>"true"}
のようにチェックボックスでの値が取り出せます。

あとはコントローラで中間テーブルを保存する

users_conntroller.rb
def create
  created_user = User.create(user_params)
  #checkboxで受け取ったパラメータはhashになっているので一つづつ取り出して保存する
  unless params[:user][:job].nil?  #nilガード
    params[:user][:job].each do |key, value|  
      if value == false  #チェックされていない場合はスキップする
        next
      else
        job_id = Job.find_by(name: key).id  #keyで探して保存するだけです
        user_job = UserJob.create(user_id: created_user.id, job_id: job_id)
      end
    end
  end

  redirect_to new_user_path
end

  private
  def user_params
    params.require(:user).permit(:name)
  end

スクリーンショット 2019-10-21 0.27.29.png

終わり

チェックボックスに入れてuser登録するとこんな感じになりましたでしょうか?
accepts_nested_attributes_forを使ってみたり、f.collection_check_boxesをつかってみたりしましたが、いまいち挙動がつかめず気持ち悪かったので、きちんと自分で書いてみました。
リファクタリングできたり、fatコントローラである点は置いておいてください。
参考になればうれしいです。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5