【Rails入門】Active Record(O/Rマッパー)でattributesを更新する方法

今日は、Ruby on Rails(以降、Rails)のActive Record(O/Rマッパー)で、attributesを更新する方法をいくつか紹介します。

・attributeやassign_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を更新するといっても様々な方法がありますので、最も適切な方法を検討し、コードを書いていきましょう。

それでは。

この記事を書いた人

侍エンジニア塾は「人生を変えるプログラミング学習」をコンセンプトに、過去多くのフリーランスエンジニアを輩出したプログラミングスクールです。侍テック編集部では技術系コンテンツを中心に有用な情報を発信していきます。

目次