LoginSignup
0
0

More than 5 years have passed since last update.

rails、君に決めた!!~9[wip]

Posted at

一連の記事の目次
rails、君に決めた!!~目次

Model

Modelの理解

ApplicationRecordとActiveRecord

Rails4から5になったことによる仕様変更の話っぽい。

Rails4以前

class User < ActiveRecord::Base
  # do something
end

Rails5

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end


class User < ApplicationRecord
  # do something
end

ActiveRecordをApplicationRecordに継承させるっていうワンクッションが挟むようになってる。今までは共通処理を実装する時、ActiveRecordに変更を加える必要があったけど、ApplicationRecordに変更を加えれば良くなった。

何が嬉しい?
→正直よくわからないけど、大元のActiveRecordに変更が入ると、これを継承する全クラスに変更が影響しちゃってよくないよね、ってことかな

命名規則

Table名
→snake_caseの複数形
モデル名
→単数形

スネークケース:単語間を_でつなぐこと

bin/rails g model user name:string email:string

generatorでモデル名を指定する時、userのように単数形にする。この時生成されるテーブル名はusersと複数形になる。

ActiveRecordの基本的なメソッド

事前準備として以下を実行

bin/rails g model user name:string:index email:string
bin/rails db:migrate

ActiveRecordのメソッドの実験場として

bin/rails c --sandbox

を使う。--sandboxってなんぞや。

--sandboxオプションをつけることでコンソール終了時にデータベースへの変更は全てロールバックされる

便利〜〜
では、個々のメソッドを見ていこう

Create

INSERTと同じ
2パターンある

  • new + save
[1] pry(main)> user = User.new(name: 'foo')
=> #<User:0x007ff566df3b18 id: nil, name: "foo", email: nil, created_at: nil, updated_at: nil>
[2] pry(main)> user.save
   (0.5ms)  SAVEPOINT active_record_1
  SQL (0.4ms)  INSERT INTO `users` (`name`, `created_at`, `updated_at`) VALUES ('foo', '2018-03-21 14:24:31', '2018-03-21 14:24:31')
   (0.2ms)  RELEASE SAVEPOINT active_record_1
=> true
  • create
[3] pry(main)> User.create(name: 'bar')
   (0.4ms)  SAVEPOINT active_record_1
  SQL (0.4ms)  INSERT INTO `users` (`name`, `created_at`, `updated_at`) VALUES ('bar', '2018-03-21 14:25:03', '2018-03-21 14:25:03')
   (0.2ms)  RELEASE SAVEPOINT active_record_1
=> #<User:0x007ff565e9e078
 id: 2,
 name: "bar",
 email: nil,
 created_at: Wed, 21 Mar 2018 14:25:03 JST +09:00,
 updated_at: Wed, 21 Mar 2018 14:25:03 JST +09:00>

何が違うのか?
→戻り値がかなり異なる
save
検証に成功した時のみtrueを返し、失敗した場合データベースに保存されずにfalseを返す

create
検証は行うが、成功失敗に関わらずモデルのインスタンスを返す。エラー情報を含んだオブジェクトが返却されるので、APIのレスポンス構築の時なんかは便利。

Read

SELECTと同じ

  • all

全レコードの取得

[4] pry(main)> User.all
  User Load (0.4ms)  SELECT `users`.* FROM `users`
=> [#<User:0x007ff567101940
  id: 1,
  name: "foo",
  email: nil,
  created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
  updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>,
 #<User:0x007ff5670b81a0
  id: 2,
  name: "bar",
  email: nil,
  created_at: Wed, 21 Mar 2018 14:25:03 JST +09:00,
  updated_at: Wed, 21 Mar 2018 14:25:03 JST +09:00>]
  • first

idが最も小さい先頭のレコードを取得。0件の場合、nilを返す

[6] pry(main)> User.first
  User Load (0.4ms)  SELECT  `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User:0x007ff5659b8068
 id: 1,
 name: "foo",
 email: nil,
 created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
 updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>
  • find 指定したidのレコードを取得。複数idも可。該当レコードがない場合、ActiveRecord::RecordNotFoundの例外を発生させる
[11] pry(main)> User.find(0)
  User Load (0.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 0 LIMIT 1
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=0
from /Users/suzukitarou/ruby/rails_app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.5/lib/active_record/core.rb:189:in `find'
[12] pry(main)> User.find(1)
  User Load (0.5ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User:0x007ff569995758
 id: 1,
 name: "foo",
 email: nil,
 created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
 updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>
  • find_by

指定条件にマッチしたレコードが存在すれば1件のみ取得し、レコードが存在しなければnilを返す

[16] pry(main)> User.find_by(name: 'bob')
  User Load (2.6ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'bob' LIMIT 1
=> nil
[17] pry(main)> User.find_by(id:1)
  User Load (0.4ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User:0x007ff56727ce00
 id: 1,
 name: "foo",
 email: nil,
 created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
 updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>

なんで1件のみ取得なんだろう。。この機能いらなくないか、、?

  • where

指定条件にマッチしたレコードを全て取得。order,limit,joinsなどとメソッドチェーンできる。とてつもなく便利。djangoはここら辺すごくわかりづらかった。。。

[27] pry(main)> User.where(created_at: Time.zone.now.all_month).where(name:'a').order(:name).limit(3)
  User Load (0.4ms)  SELECT  `users`.* FROM `users` WHERE (`users`.`created_at` BETWEEN '2018-03-01 00:00:00' AND '2018-03-31 23:59:59') AND `users`.`name` = 'a' ORDER BY `users`.`name` ASC LIMIT 3
=> []

Update

1レコード単位で更新を行う場合

[34] pry(main)> user = User.first
  User Load (0.9ms)  SELECT  `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User:0x007ff56708b3f8
 id: 1,
 name: "foo",
 email: nil,
 created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
 updated_at: Wed, 21 Mar 2018 14:24:31 JST +09:00>
[35] pry(main)> user.update(name: 'rails')
   (0.3ms)  SAVEPOINT active_record_1
  SQL (0.6ms)  UPDATE `users` SET `name` = 'rails', `updated_at` = '2018-03-21 16:11:28' WHERE `users`.`id` = 1
   (0.2ms)  RELEASE SAVEPOINT active_record_1
=> true
[36] pry(main)> user
=> #<User:0x007ff56708b3f8
 id: 1,
 name: "rails",
 email: nil,
 created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
 updated_at: Wed, 21 Mar 2018 16:11:28 JST +09:00>

レコードをまとめて更新する場合

[40] pry(main)> User.update(name:'foo')
  User Load (0.5ms)  SELECT `users`.* FROM `users`
   (0.2ms)  SAVEPOINT active_record_1
  SQL (0.4ms)  UPDATE `users` SET `name` = 'foo', `updated_at` = '2018-03-21 16:12:43' WHERE `users`.`id` = 1
   (0.2ms)  RELEASE SAVEPOINT active_record_1
   (0.2ms)  SAVEPOINT active_record_1
  SQL (0.5ms)  UPDATE `users` SET `name` = 'foo', `updated_at` = '2018-03-21 16:12:43' WHERE `users`.`id` = 2
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> [#<User:0x007ff5661b9848
  id: 1,
  name: "foo",
  email: nil,
  created_at: Wed, 21 Mar 2018 14:24:31 JST +09:00,
  updated_at: Wed, 21 Mar 2018 16:12:43 JST +09:00>,
 #<User:0x007ff5661b9640
  id: 2,
  name: "foo",
  email: nil,
  created_at: Wed, 21 Mar 2018 14:25:03 JST +09:00,
  updated_at: Wed, 21 Mar 2018 16:12:43 JST +09:00>]

Delete

SQLのDELETEに相当。対応するのはdeleteとdestroy。deleteはSQLのDELETEを発行するだけの低レベルなメソッド。対してdestroyはdeleteを行う前後にコールバックを実行する。
ここでのコールバックは、関連テーブルのオブジェクトを同時に削除したり、オブジェクトの状態によっては削除処理を中断するといった処理のこと。

1件単位の削除

# コールバック付き削除
user.destroy

# コールバックを実行しない削除
user.delete

複数レコードの同時削除

# コールバックを実行して削除
User.destroy_all

# コールバックを実行せずに削除
User.delete_all

destroy_allはコールバックを実行しながら、一行ずつDELETEする
→遅い

delete_allは1本のSQLで一括削除
→高速

今までのDELETEは物理削除と呼び、データベースの復元ができないので、事前に別のストレージにデータを退避させておくと安心。

対して論理削除とは、deleted_atのような削除フラグをカラムに持ち、UPDATEによって削除処理を代替するもの。

deleted_atがnilのものを抽出すれば未削除レコードのみを取得できる。確かに!!
検索とか遅くなりそうではあるけど笑

検証(バリデーション)

モデルオブジェクトがデータベースに保存される前に、オブジェクトが有効かどうかチェックする仕組み

user.rb

class User < ApplicationRecord
  validates :name, uniqueness: true
  validates :email, presence: true
end

エラーメッセージオブジェクト

モデルオブジェクトのライフサイクルとコールバック

そもそもコールバックとは

コールバックとは、オブジェクトのライフサイクル期間における特定の瞬間に呼び出されるメソッドのことです。コールバックを利用することで、Active Recordオブジェクトが作成/保存/更新/削除/検証/データベースからの読み込み、などのイベント発生時に常に実行されるコードを書くことができます。

モデルオブジェクトが生成されてからINSERT,UPDATE,DELETEされる間のよしななタイミングでいろんなコールバックメソッドを実行できるよ〜ってことっぽい。

表でまとめる。(around_xxxはよろしくないらしいのでとばす)

CREATE UPDATE DELETE
before_validation before_validation before_destroy
VALIDATION VALIDATION DELETE
after_validation after_validation after_destroy
before_save before_save
before_create before_update
INSERT UPDATE
after_create after_update
after_save after_save

例えば、コールバックを利用して、新規ユーザ作成後にメールを送信する処理は以下のようになる

class User < ApplicationRecord
  after_create :send_email

  private

  def send_email
    # do something
  end
end

callback内での処理の中断

saveとsave!の使い分け

ActiveRecordでは、saveとsave!、updateとupdate!のように!つきのメソッドが用意されている。

処理の違いは以下
save
→成功ならtrue,失敗ならfalseを返す

sava!
→成功ならtrue,失敗なら例外を発生させる

どう使い分けるか?
saveは成功失敗に関わらず後続処理が行われてしまう。
save!は失敗したら止まる。

成功・失敗時の処理をif文などで定義したい場合
→save

失敗した場合に後続の処理を定義するのが難しい、またはそもそも失敗を想定していない場合
→save!

0
0
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
0
0