LoginSignup
157
161

More than 5 years have passed since last update.

【Rails】has_many throughな関係で、複数レコードを new/create する時の書き方

Last updated at Posted at 2014-02-26

new/create時に関連テーブルも更新する記述について、忘れていたのでまとめてみました。(意外と参考書とかにも載っておらず・・・)

今回利用しているバージョンは下記。
Ruby -> 2.1.0
rails -> 4.0.2

20140307 記述を一部修正しました

元記事

下記エントリの転載になります。
【Rails】has_many throughな関係で、複数レコードを new/create する時の書き方 - rokuro Fire

やりたいこと

  • postを1件更新するときに関連テーブルを複数件数一気に更新する

Model

  • 各モデルの関係を定義。ここはいつものhas_many through。
  • posts <-> author_posts <-> authors
  • authorsは "name" カラムを持つ
  • formでネストさせるので、accepts_nested_attributes_for も設定します。
app/models/post.rb
class Post < ActiveRecord::Base
    has_many :author_posts
    has_many :authors, :through => :author_posts

    accepts_nested_attributes_for :authors
end
app/models/author.rb
class Author < ActiveRecord::Base
    has_many :author_posts
    has_many :posts, :through => :author_posts
end
app/models/author_post.rb
class AuthorPost < ActiveRecord::Base
    belongs_to :post
    belongs_to :author
end

まずはirbで動作を確認

# postのインスタンスを作成
irb > post = Post.new
=> #<Post id: nil, title: nil, created_at: nil, updated_at: nil>

# 空のインスタンスを確認
irb > post
=> #<Post id: nil, title: nil, created_at: nil, updated_at: nil>

# post.author_postsの確認
# 空のArray
irb > post.author_posts
=> #<ActiveRecord::Associations::CollectionProxy []>

# post.authorsの確認
# 空のArray
irb > post.authors
=> #<ActiveRecord::Associations::CollectionProxy []>

# buildで、postの関連テーブルauthorのインスタンスを作成
irb > post.authors.build
=> #<Author id: nil, name: nil, created_at: nil, updated_at: nil>

# post.authorsのArrayに、作成したインスタンスが格納されている
irb > post.authors
=> #<ActiveRecord::Associations::CollectionProxy [#<Author id: nil, name: nil, created_at: nil, updated_at: nil>]>

# 試しにもう一回buildしてみる
irb > post.authors.build
=> #<Author id: nil, name: nil, created_at: nil, updated_at: nil>

# post.authorsのArrayに、2個目のインスタンスがpushされている
irb > post.author
=> #<ActiveRecord::Associations::CollectionProxy [#<Author id: nil, name: nil, created_at: nil, updated_at: nil>, #<Author id: nil, name: nil, created_at: nil, updated_at: nil>]>

# post.author_posts のArrayはこの時点では空
irb > post.author_posts
=> #<ActiveRecord::Associations::CollectionProxy []>

# saveしてみる
# 関連テーブルにもINSERTされている
irb > post.save
   (0.5ms)  BEGIN
  SQL (4.4ms)  INSERT INTO `posts` (`created_at`, `updated_at`) VALUES ('2014-02-23 08:20:58', '2014-02-23 08:20:58')
  SQL (0.2ms)  INSERT INTO `authors` (`created_at`, `updated_at`) VALUES ('2014-02-23 08:20:58', '2014-02-23 08:20:58')
  SQL (0.3ms)  INSERT INTO `author_posts` (`author_id`, `created_at`, `post_id`, `updated_at`) VALUES (3, '2014-02-23 08:20:58', 10, '2014-02-23 08:20:58')
  SQL (0.2ms)  INSERT INTO `authors` (`created_at`, `updated_at`) VALUES ('2014-02-23 08:20:58', '2014-02-23 08:20:58')
  SQL (0.2ms)  INSERT INTO `author_posts` (`author_id`, `created_at`, `post_id`, `updated_at`) VALUES (4, '2014-02-23 08:20:58', 10, '2014-02-23 08:20:58')
   (2.2ms)  COMMIT
=> true

# save後に post.author_posts を再確認
# Arrayに値が入っている
irb > post.author_posts
=> #<ActiveRecord::Associations::CollectionProxy [#<Author id: 1, name: nil, created_at: "2014-02-23 08:41:41", updated_at: "2014-02-23 08:41:41">, #<Author id: 2, name: nil, created_at: "2014-02-23 08:41:41", updated_at: "2014-02-23 08:41:41">]>

関連オブジェクトの値を更新する際は、post.authors.build のようにする必要があります 。複数個の場合は複数回buildしてあげればよさそうです。
上記を元に、View、Controllerを作っていきます。

View(フォーム)

app/views/posts/_form.html.erb
<%= form_for(@post) do |f| %>

    <% @post.authors.each do |author| %>
        <%= f.fields_for :authors, author do |author_field| %>
            <%= author_field.text_field :name %>
        <% end %>
    <% end %>

<% end %>
"authors_attributes[]"の部分は、普通は:authorsと書くようですが・・・ 複数個更新するときには上記のように書くと、同名のパラメータを配列に格納してくれます。 また、authorsの"name"を更新したいので、text_fieldには":name"と記述します。

追記):authors でも動作しました。

authors_attributesのパラメータ

params[:post][:authors_attributes]の中身は下記になります。

"authors_attributes"=>[{"name"=>"著者1"}, {"name"=>"著者2"}, {"name"=>"著者3"}]}

(実験)f.fields_forの引数を :authors にした場合

"authors_attributes"=>{"0"=>{"name"=>"あ"}, "1"=>{"name"=>"い"}, "2"=>{"name"=>"う"}}}
keyに個数を表す数値が入ってきてしまいました。 これが気持ち悪くて配列で取得するようにしています。

追記)こちらで正しいようです。

Controller

app/controllers/posts_controller.rb
def new
    @post = Post.new

    # 今回は分かりやすく、authorは固定で3枠作成
    # 1postで最大3author追加出来る

    3.times {
        # 関連オブジェクトをbuild
        @post.authors.build
    }
end

def create
    @post = Post.new(post_params);
    @post.save
end

# strong parameters
private
def post_params
    params.require(:post).permit(:title, authors_attributes: [:name])
end
本当はcreateに何も追記したくなかったのですが・・・(これ以外の方法がわかりませんでした。) また、strong parametersのホワイトリストに ":authors_attributes => []" を追記しています。

追記)上記のようにstrong parametersに記述をすることで、余計な処理を書かずに更新出来ました。
ただし、データを取り出して何らかの加工をする場合には、Arrayで取得しても良さそうです。

参考

157
161
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
157
161