おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

【Rails】オブジェクトを作成したり検索したり更新したり保存したり

Ruby on Railsではデータベースとの様々なやりとりをActice Recordという仕組みを使って行いますが、よく使われる方法をざっとご紹介です。

今回利用するテーブル情報はこちらです。

Table "public.hoges"
   Column   |            Type             | Collation | Nullable |              Default              
------------+-----------------------------+-----------+----------+-----------------------------------
 id         | bigint                      |           | not null | nextval('hoges_id_seq'::regclass)
 name       | character varying           |           |          | 
 age        | integer                     |           |          | 
 gender     | character varying           |           |          | 
 birthday   | date                        |           |          | 
 email      | character varying           |           |          | 
 created_at | timestamp without time zone |           | not null | 
 updated_at | timestamp without time zone |           | not null | 
Indexes:
    "hoges_pkey" PRIMARY KEY, btree (id)

オブジェクトを作成する

オブジェクトを作成するときは、newbuildを使います。

> Hoge.new
=> #<Hoge:0x00007fe9b5bbce10 id: nil, name: nil, age: nil, gender: nil, birthday: nil, email: nil, created_at: nil, updated_at: nil>
# hogeに紐づく記事データを作成する(1 : Nの関係)
def new
  hoge = Hoge.find params[:id]
  @hoge = hoge.articles.build(strong_parameter)
end

# hogeに紐づく家族構成データを作成する(1 : 1の関係)
def new
  hoge = Hoge.find params[:id]
  @hoge = hoge.build_family_structure(strong_parameter)
end

newは単体のモデルを生成する時、buildはテーブルの関係が1 : N(has_many)の場面で使います。

ログインが前提になるようなサービスだとnewを使う場面は、あんまり無いと思います。

build_モデル名はテーブルの関係が1 : 1(has_one)の場面で利用します。

【20191002追記】

より詳細な情報・書き方まとめてみました!

opiyotan.hatenablog.com

オブジェクトを検索する

オブジェクトを検索する = SQLで言うwhere文を実行する処理です。

  • find
  • find_by
  • find_by!
  • first
  • last
  • where
# idが1のものを1件取得
> Hoge.find 1
  Hoge Load (0.5ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
=> #<Hoge:0x00007fe9b645f4a0 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00>

# nameが'opiyo'のものを1件取得
> Hoge.find_by(name: 'opiyo')
  Hoge Load (0.4ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2  [["name", "opiyo"], ["LIMIT", 1]]
=> #<Hoge:0x00007fe9d4d21e30 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00>

# 一番最初に登録されたものを取得
> Hoge.first
  Hoge Load (0.3ms)  SELECT  "hoges".* FROM "hoges" ORDER BY "hoges"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> #<Hoge:0x00007fe9b6242e60 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00>

# 一番最後に登録されたものを取得
> Hoge.last
  Hoge Load (0.4ms)  SELECT  "hoges".* FROM "hoges" ORDER BY "hoges"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<Hoge:0x00007fe9b61b97c8 id: 2, name: "tako", age: 20, gender: nil, birthday: Sat, 23 Oct 1999, email: nil, created_at: Tue, 01 Oct 2019 21:50:26 JST +09:00, updated_at: Tue, 01 Oct 2019 21:50:26 JST +09:00>

# 条件に一致するものを複数件取得
> Hoge.where(gender: nil)
  Hoge Load (0.4ms)  SELECT "hoges".* FROM "hoges" WHERE "hoges"."gender" IS NULL
=> [#<Hoge:0x00007fe9b59b6ff8 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00>,
 #<Hoge:0x00007fe9b59b6e40 id: 2, name: "tako", age: 20, gender: nil, birthday: Sat, 23 Oct 1999, email: nil, created_at: Tue, 01 Oct 2019 21:50:26 JST +09:00, updated_at: Tue, 01 Oct 2019 21:50:26 JST +09:00>]

# データが見つからなかった時
> Hoge.find_by!(name: 'opi')
  Hoge Load (0.4ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2  [["name", "opi"], ["LIMIT", 1]]
ActiveRecord::RecordNotFound: Couldn't find Hoge

少しややこしいのが、find_bywhereです。find_byは必ず1件のデータのみ返却しますので1件を保証される場合はこちらを使います。

また、find_by!にすることで1件も見つからない場合は例外を発生させることもあります。

joingrouporderなども出来ますので、こちらはまた別の記事でまとめてみたいと思います。

【20191007追記】

より詳細な情報・書き方まとめてみました!

opiyotan.hatenablog.com

オブジェクトの保存・更新

オブジェクトの保存・更新は種類がいっぱいあります。

  • save
  • save!
  • update
  • update!
  • update_attribute
  • update_attributes
  • update_attributes!
  • update_column
  • update_columns
  • update_all

全てデータの保存や更新する処理になりますが、違いのポイントとしては以下があります。

  • 戻り値の違い「true/false」 or 「例外」
  • コールバックアクション(before_action/after_action)の有無
  • バリデーションの有無

!がついた場合は例外を返します。

update_columnはコールバックやバリデーションを無視しますので、ajaxやrakeタスクなど非同期で処理している時に使うことが多いと思います。が、基本的にはupdate_attributesを使います。

【20191007追記】

より詳細な情報・書き方まとめてみました!

opiyotan.hatenablog.com

オブジェクトの削除

削除する処理はdestroydeleteの二つになります。

  • destroy
  • delete
  • destroy_all
  • delete_all

大きな違いは、destroyの場合は関連するデータも削除しますがdeleteの場合はそのデータのみを削除します。

【20191003追記】

より詳細な情報・書き方まとめてみました!

opiyotan.hatenablog.com

合わせて一本!

探して作って、探して保存してを実現する便利なメソッドもあるのでそれもご紹介です!

  • find_or_created_by!
  • find_or_initialize_by
# findして、あればデータを無ければnew
> Hoge.find_or_initialize_by(name: 'opiyo')
  Hoge Load (0.5ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2  [["name", "opiyo"], ["LIMIT", 1]]
=> #<Hoge:0x00007fe9d4d57418 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00>

> Hoge.find_or_initialize_by(name: 'opiy')
  Hoge Load (0.5ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2  [["name", "opiy"], ["LIMIT", 1]]
=> #<Hoge:0x00007fe9d4c692b8 id: nil, name: "opiy", age: nil, gender: nil, birthday: nil, email: nil, created_at: nil, updated_at: nil>

# findして、あればデータを無ければcreate
> Hoge.find_or_create_by!(name: 'opiyo', age: 30)
  Hoge Load (0.3ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 AND "hoges"."age" = $2 LIMIT $3  [["name", "opiyo"], ["age", 30], ["LIMIT", 1]]
=> #<Hoge:0x00007fe9b6190788 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00>

> Hoge.find_or_create_by!(name: 'opiy', age: 40)
  Hoge Load (0.4ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 AND "hoges"."age" = $2 LIMIT $3  [["name", "opiy"], ["age", 40], ["LIMIT", 1]]
   (0.2ms)  BEGIN
  Hoge Create (0.3ms)  INSERT INTO "hoges" ("name", "age", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "opiy"], ["age", 40], ["created_at", "2019-10-01 13:27:21.287452"], ["updated_at", "2019-10-01 13:27:21.287452"]]
   (4.0ms)  COMMIT
=> #<Hoge:0x00007fe9d17bc358 id: 3, name: "opiy", age: 40, gender: nil, birthday: nil, email: nil, created_at: Tue, 01 Oct 2019 22:27:21 JST +09:00, updated_at: Tue, 01 Oct 2019 22:27:21 JST +09:00>

これ以外にも基本的に上記で紹介した処理は繋げて書くことが可能です。

> Hoge.find_by(name: 'opiyo')
  Hoge Load (0.5ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2  [["name", "opiyo"], ["LIMIT", 1]]
=> #<Hoge:0x00007fe9d4811508 id: 1, name: "opiyo", age: 30, gender: nil, birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Mon, 30 Sep 2019 20:47:43 JST +09:00>

> Hoge.find_by(name: 'opiyo').update_attributes(gender: '男性')
  Hoge Load (0.4ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2  [["name", "opiyo"], ["LIMIT", 1]]
   (0.2ms)  BEGIN
  Hoge Update (0.5ms)  UPDATE "hoges" SET "gender" = $1, "updated_at" = $2 WHERE "hoges"."id" = $3  [["gender", "\xE7\x94\xB7\xE6\x80\xA7"], ["updated_at", "2019-10-01 13:29:00.694154"], ["id", 1]]
   (0.7ms)  COMMIT
=> true

> Hoge.find_by(name: 'opiyo')
  Hoge Load (0.6ms)  SELECT  "hoges".* FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2  [["name", "opiyo"], ["LIMIT", 1]]
=> #<Hoge:0x00007fe9b8121070 id: 1, name: "opiyo", age: 30, gender: "男性", birthday: Fri, 04 Aug 1989, email: nil, created_at: Mon, 30 Sep 2019 20:47:43 JST +09:00, updated_at: Tue, 01 Oct 2019 22:29:00 JST +09:00>

ざっとよく使われるものを紹介してきましたが、どんな場面でどのように使うのか。こんな時はどうするのかなど一杯あると思いますのでまた別の機会で、 深掘りした内容もまとめていきたいと思います。