今日は、Ruby on Rails(以降、Rails)のActive Record(O/Rマッパー)で、attributesを更新する方法をいくつか紹介します。
・has_manyの関係を持つ入れ子のデータを一括して更新する方法はないものか?
・データベースに保存するまでもないようなattributesを簡単に保持しておくことはできない?
といった疑問に答える記事になっています。
例によって、動作を理解するためのWebアプリを作ることから始めていますので、実際に操作をしながら、動作を理解していってください!
それでは、始めましょう。
Active RecordおよびO/Rマッパーとは
Active Recordは、データベースに保存されるデータを取り扱うためのコンポーネントです。
Railsでは、Active Recordを通して、データベースからデータを取り出したり、データベースにデータを保存したりします。
O/Rマッパーは、データベースとプログラムを橋渡しする役目を担うモノです。
データベースに保存されているデータをプログラムで利用するには、SQL文を利用してデータベースからデータを取り出し、そのデータをプログラムで扱う変数に格納します。
この処理をスムーズに行うモノがO/Rマッパーです。
RailsではActive RecordにO/Rマッパーが含まれているため、Active Recordを使えば、データベースからデータを取り出したり、データベースにデータを保存したりといった処理がスムーズに行える、と考えればよいでしょう。
attributesとは
この記事で取り扱うattributesは、モデルに含まれるすべての属性を指します。
以下のコマンドでUserモデルを作成したときに、
bin/rails generate scaffold User name:string
「@user.name」でアクセスできる「name」のことを、attributesと呼んでいます。
動作を理解するためのWebアプリを作成する
attributesの取り扱いを理解するために、RailsをインストールしてWebアプリを作りましょう。
(1)Railsをインストールします。
私は、以下の記事を参考に、VirtualBoxで作成した仮想パソコンにインストールしたLinux Mintに、Railsの開発環境を作成しました。
基本的には記事の手順に従って操作しますが、app/samurai/sample1ディレクトリを作成する代わりに、app/samurai/attributes-demoディレクトリを作成しました。
Railsを起動して、ブラウザで画面が表示されることを確認したら、いったんRailsを終了してから次に進みます。
Linux Mintのインストールについては、以下の記事で詳しく説明しています。
(2)Gemfileの最終行に以下の内容を追記します。
gem 'hirb' gem 'hirb-unicode'
Hirbについては、以下の記事で詳しく説明していますので、あわせてご覧ください。
(3)新しい「端末」を起動して、以下のコマンドを1行ずつ順番に入力します。
cd app/samurai/attributes-demo bundle install bin/rails generate scaffold User name:string bin/rails generate scaffold Post user_id:integer title:string month:integer bin/rails db:migrate bin/rails console
(4)以下のコードを1行ずつ順番に入力します。
User.create(name:"山田太郎") User.create(name:"長瀬来") User.create(name:"立川裕美") User.create(name:"前田達郎") User.create(name:"細川修二") User.create(name:"木村拓磨") Post.create(user_id:5, title:"楽しい休日の過ごし方", month:3) Post.create(user_id:1, title:"先日の旅行での話", month:2) Post.create(user_id:3, title:"昨日の出来事", month:12) Post.create(user_id:3, title:"山登りに行きました", month:8) Post.create(user_id:4, title:"友人が結婚しました", month:4) Post.create(user_id:2, title:"最近少し気になったこと", month:1) Post.create(user_id:4, title:"ランニングのコツ", month:9) Post.create(user_id:3, title:"Ruby on Railsの日", month:9) exit
UserテーブルとPostテーブルにデータが入力され、Railsコンソールが終了します。
次にUserテーブルとPostテーブルを関連付けます。
(5)app/models/user.rbを以下のように編集します。
変更前:
class User < ApplicationRecord end
変更後:
class User < ApplicationRecord has_many :posts accepts_nested_attributes_for :posts end
(6)app/models/post.rbを以下のように編集します。
変更前:
class Post < ApplicationRecord end
変更後:
class Post < ApplicationRecord belongs_to :user end
これで、準備ができました。
データのattributesを更新する
データベースのデータのattributesを更新する方法を説明します。
まずは、保存するタイミングが異なる2つの方法を紹介しましょう。
データのattributesを一括で更新してあとで保存する場合
データのattributesを一括で更新して、あとでデータベースに保存する方法を説明します。
具体的には、attributesまたはassign_attributesを使ってattributesを一括で更新し、saveを使ってデータベースに保存します。
(1)「端末」で以下のコマンドを入力します。
bin/rails console
(2)以下のコードを1行ずつ順番に入力します。
Hirb.enable @post = Post.find(1) @post.attributes = { title: '日曜日と祝日の朝ごはん', month: 5 } @post
1番目のPostデータのattributes(titleとmonth)が一括で更新されます。
実行結果:
+----+---------+------------------------+-------+-------------------------+-------------------------+ | id | user_id | title | month | created_at | updated_at | +----+---------+------------------------+-------+-------------------------+-------------------------+ | 1 | 5 | 日曜日と祝日の朝ごはん | 5 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:20:46 UTC | +----+---------+------------------------+-------+-------------------------+-------------------------+ 1 row in set
なお、3行目のコードは、以下のコードでも同じ動作になります。
@post.assign_attributes({ title: '日曜日と祝日の朝ごはん', month: 5 })
ただし、この時点ではデータベースには保存されていません。
いったんRailsコンソールを再起動して、1番目のPostデータを確認してみましょう。
(3)以下のコードを入力します。
exit
Railsコンソールが終了します。
(4)以下のコマンドを入力します。
bin/rails console
(5)以下のコードを入力します。
Hirb.enable @post = Post.find(1)
実行結果:
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] +----+---------+----------------------+-------+-------------------------+-------------------------+ | id | user_id | title | month | created_at | updated_at | +----+---------+----------------------+-------+-------------------------+-------------------------+ | 1 | 5 | 楽しい休日の過ごし方 | 3 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:20:46 UTC | +----+---------+----------------------+-------+-------------------------+-------------------------+ 1 row in set
残念ながら元の内容に戻っていますので、先ほどのattributesで変更した内容が保存されていないことがわかります。
(6)以下のコードを1行ずつ順番に入力します。
@post.attributes = { title: '日曜日と祝日の朝ごはん', month: 5 } @post.save() exit
@post.save()を実行したため、今度はデータベースに保存されました。
同じようにいったんRailsコンソールを再起動して確認してみましょう。
(7)以下のコマンドを入力します。
bin/rails console
(8)以下のコードを1行ずつ順番に入力します。
Hirb.enable @post = Post.find(1)
実行結果:
Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] +----+---------+------------------------+-------+-------------------------+-------------------------+ | id | user_id | title | month | created_at | updated_at | +----+---------+------------------------+-------+-------------------------+-------------------------+ | 1 | 5 | 日曜日と祝日の朝ごはん | 5 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:28:07 UTC | +----+---------+------------------------+-------+-------------------------+-------------------------+ 1 row in set
今度はしっかりと保存されていますね!
データのattributeを一括で更新してすぐに保存する場合
データのattributeを一括で更新して、すぐにデータベースに保存する方法を説明します。
具体的には、update_attributesを使ってデータを一括で更新します。
(1)「端末」で以下のコマンドを入力します。
bin/rails console
(2)以下のコードを1行ずつ順番に入力します。
@post = Post.find(1) @post.update_attributes({ user_id: 2, title: '金曜日のディナー', month: 2 }) exit
例によって、Railsコンソールを再起動して確認してみましょう。
(3)以下のコマンドを入力します。
bin/rails console
(4)以下のコードを入力します。
Hirb.enable @post = Post.find(1)
実行結果:
Post Load (0.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] +----+---------+------------------+-------+-------------------------+-------------------------+ | id | user_id | title | month | created_at | updated_at | +----+---------+------------------+-------+-------------------------+-------------------------+ | 1 | 2 | 金曜日のディナー | 2 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:33:19 UTC | +----+---------+------------------+-------+-------------------------+-------------------------+ 1 row in set
しっかりと保存されています。
(5)以下のコードを入力します。
exit
ストロングパラメータを利用する
ストロングパラメータ(Strong Parameter)は、送られてきたパラメータを安全に、データのattributesに反映するための仕組みです。
ストロングパラメータを使用せずに発生するぜい弱性を、マスアサインメント(Mass Assignment)ぜい弱性と呼びます。
ストロングパラメータやマスアサインメントぜい弱性については、以下の記事でも触れていますので、あわせてご覧ください。
入れ子のデータのattributesをまとめて更新する
ここからは、入れ子のデータのattributesを、以下のようにまとめて更新する方法を紹介します。
@user.attributes = { name:"侍エンジニアさん", posts_attributes:[ {id:3, title: "きのうのできごと"}, {id:4, title:"あしたのよてい", month: 5}, {id:8, title:"Railsのひ", month: 10} ] }
(1)以下のコマンドを入力します。
bin/rails console
(2)以下のコードを1行ずつ順番に入力します。
Hirb.enable @user = User.find(3) @user.posts
実行結果:
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 3]] +----+---------+--------------------+-------+-------------------------+-------------------------+ | id | user_id | title | month | created_at | updated_at | +----+---------+--------------------+-------+-------------------------+-------------------------+ | 3 | 3 | 昨日の出来事 | 12 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:20:46 UTC | | 4 | 3 | 山登りに行きました | 8 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:20:46 UTC | | 8 | 3 | Ruby on Railsの日 | 9 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:20:46 UTC | +----+---------+--------------------+-------+-------------------------+-------------------------+ 3 rows in set
3つのPostデータが表示されます。
この3つのPostデータのattributesをまとめて更新します。
(3)以下のコードを1行ずつ順番に入力します。
@user.attributes = { name:"侍エンジニアさん", posts_attributes:[ {id:3, title: "明日の予定"}, {id:4, title: "海水浴に行きましょう"}, {id:8, title: "Ruby on Rails勉強会", month: 10} ] } @user.posts
実行結果:
+----+---------+----------------------+-------+-------------------------+-------------------------+ | id | user_id | title | month | created_at | updated_at | +----+---------+----------------------+-------+-------------------------+-------------------------+ | 3 | 3 | 明日の予定 | 12 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:20:46 UTC | | 4 | 3 | 海水浴に行きましょう | 8 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:20:46 UTC | | 8 | 3 | Ruby on Rails勉強会 | 10 | 2018-07-17 14:20:46 UTC | 2018-07-17 14:20:46 UTC | +----+---------+----------------------+-------+-------------------------+-------------------------+ 3 rows in set
このコードが予想どおりに動作するのは、app/models/user.rbに、accepts_nested_attributes_for :postsを書いたためです。
では、データベースに保存してから、Railsコンソールを終了しましょう。
(4)以下のコードを1行ずつ順番に入力します。
@user.save exit
Virtual Attributesを利用する
Virtual Attributesは、データベースに存在せずModelにだけ存在するattributesのことです。
プログラムに存在していた方が便利なattributesを記録しておくために使用します。
ここでは、簡単なVirtual Attributesとして、Userテーブルのnameの文字数を、name_lengthに一時的に記録してみましょう。
(1)app/models/user.rbを以下のように編集します。
変更前:
class User < ApplicationRecord has_many :posts accepts_nested_attributes_for :posts end
変更後:
class User < ApplicationRecord has_many :posts accepts_nested_attributes_for :posts attr_accessor :name_length end
(2)「端末」で以下のコマンドを1行ずつ順番に入力します。
bin/rails console
(3)以下のコードを1行ずつ順番に入力します。
@user = User.find(1) @user.name_length = @user.name.length
実行結果:
=> 4
@user.name_lengthに値を登録できるのは、app/models/user.rbに、attr_accessor :name_lengthを追加したためです。
たとえば、@user.dummy = 4といったように適当なattributesをでっち上げてデータを登録しようとしても、エラーメッセージが表示されます。
なお、name_lengthについてはデータベースに保存されていないため、Railsコンソールを再起動すると、nilに戻りますので、注意してください。
attr_accessorについては、以下の記事でも触れていますので、興味のある方はご覧いただければと思います。
まとめ
今回はActive Record(O/Rマッパー)とデータのattributesについて説明し、データのattributesを更新する方法をいくつか紹介しました。
初めに、データを1つ取り出して、そのデータのattributesを更新する方法を紹介しました。
attributesまたはassign_attributesを使うだけでは、データベースに保存されていないため、最後にsaveが必要でした。
また、update_attributesを使うと、データベースに保存されるため、最後のsaveは不要でした。
少し応用的な使いかたとして、入れ子の(has_manyの関係にある)データを、attributes、assign_attributes、またはupdate_attributesを使って一括で更新するためには、accepts_nested_attributes_forを指定する必要があることも説明しましたね。
最後に、データベースには保存する必要がないものの、あると便利なattributes(Virtual Attributesと呼ばれるattributes)を定義する方法を説明しました。
attributesを更新するといっても様々な方法がありますので、最も適切な方法を検討し、コードを書いていきましょう。
それでは。