読者です 読者をやめる 読者になる 読者になる

RailsでActiveRecord使った時のassociationのお話

今週、諸事情でrails書いてる時間が多くて、いろいろ上手くいった部分とか上手く出来なかった部分とかあったのをちょっと書いてみる。

多対多のassociationがあるmodelの永続化

以下の様な定義のPage, Image, PageImage modleを考えるとする。

class Page < ActiveRecord::Base
  has_many :page_images
  has_many :images, through: :page_images
end

class Image < ActiveRecord::Base
  has_many :page_images
  has_many :pages, through: :page_images
end

class PageImage < ActiveRecord::Base
  belongs_to :page
  belongs_to :image
end

中間テーブルのmodelであるPageImageを介して、PageとImageの間に多対多の関係にある事が分かる。

ここで、想定されるケースとしてpageとimage1, mage2がそれぞれurlを渡されて初期化された場合を考える。初期化されただけだとまだDBにデータは存在しない。

page = Page.new(url: 'http://my-site.co.jp/article')
image1 = Image.new(url: 'http://my-site.co.jp/image1.jpg')
image1 = Image.new(url: 'http://my-site.co.jp/image1.jpg')
# DBにはデータ無し
Page.all.size #=> 0
Image.all.size #=> 0
PageImage.all.size #=> 0

この時点だと、page.imagesにimageを追加してもassociationがはられる(page_imageがinitializeされる)だけでDBには保存されない。

page.images = [image1, image2]
# まだDBにはデータ無し
Page.all.size #=> 0
Image.all.size #=> 0
PageImage.all.size #=> 0

pageをsaveすると、article,image,article_imageが全て永続化される。

page.save
Page.all.size #=> 1
Image.all.size #=> 2
PageImage.all.size #=> 2

page.saveの時点でtransactionでDBへの永続化処理が走って、image, page, image_pageのうち1つでも永続化に失敗したらrollbackしてくれるから嬉しい。

page.url = 'invalid'
page.save #=> false
# RollbackされるのでDBにデータは保存されない
Page.all.size #=> 0
Image.all.size #=> 0
PageImage.all.size #=> 0

ここまでは求めてる通りの振る舞いで大変良い。

上手く行かなかった話

ただ、中間テーブルに何かしらのプロパティを持たしてる場合に、綺麗に初期化処理が書けなくてモヤっとした。

# 各modelを初期化
page = Page.new(url: 'http://my-site.co.jp/article')
image1 = Image.new(url: 'http://my-site.co.jp/image1.jpg')
image1 = Image.new(url: 'http://my-site.co.jp/image1.jpg')
# association作る。
page.images = [image1, image2]
# associationが出来たので、eachループでそれぞれにプロパティ設定。この処理に冗長さを感じる。
page.page_images.each { |page_image| page_image.used = true }
# page, image, page_imageの永続化
page.save

出来れば、associationを直接作ってpageにぶち込みたい。

対処法を思いついた

このへんまで考えてから、page.page_imagesに直接page_imageをぶちこんだらいいんじゃないかなーとか思った。

# 各modelを初期化
page = Page.new(url: 'http://my-site.co.jp/article')
image1 = Image.new(url: 'http://my-site.co.jp/image1.jpg')
image1 = Image.new(url: 'http://my-site.co.jp/image1.jpg')
# association作る。
page.page_images = [imaeg1, image2].map { |image| PageImage.new(image: image, used: true) }
# page, image, page_imageの永続化
page.save

これで上手くいった。予想通りで嬉しい。

Page.all.size #=> 1
Image.all.size #=> 2
PageImage.all.size #=> 2
PageImage.all
#=> [#<PageImage id: 1, page_id: 1, image_id: 1, created_at: "2014-08-02 15:05:24", updated_at: "2014-08-02 15:05:24", used: true>,
#<PageImage id: 2, page_id: 1, image_id: 2, created_at: "2014-08-02 15:05:24", updated_at: "2014-08-02 15:05:24", used: true>]

まとめ

Rails書いてると「これやってみたらどうなるんだろう?」とか思って試した事がサクッと上手くいったりするので、嬉しさを感じるタイミングが多い気がする。

ちなみに、本当にどうでも良い話。

最近友達に誘われてボルダリングに行くんだけど、筋力に頼りまくってるせいで上手く登れるの最初だけで後半だんだん登れなくなってくるのがつらい。技術が欲しい。体重移動とかそういうやつ。