マイグレーションを使う
テーブル構造の変更はマイグレーションへ記録します。後から別の環境でも同じ変更を再現できるようにするためです。
bin/rails generate model Article title:string body:text published:boolean
bin/rails db:migrate
bin/rails db:rollbackdb:rollbackは直前の変更を戻す開発用の操作です。共有済みのマイグレーションを勝手に書き換えると環境ごとの差が生まれるため、公開後の変更は新しいマイグレーションで表現します。
保存と検索
article = Article.new(title: "Rails入門", body: "本文", published: true)
article.save
Article.create!(title: "2つ目", body: "本文", published: false)
Article.where(published: true).order(created_at: :desc)
Article.find_by(title: "Rails入門")
Article.where("title LIKE ?", "%Rails%").pluck(:title)["Rails入門"]
検索条件はハッシュやプレースホルダーを使います。文字列を連結してSQLを作ると入力値がSQLの一部として解釈される危険があるため、値は条件の引数として渡します。
関連付けを作る
ユーザーが複数の記事を持つなら、UserとArticleの関係をモデルへ書きます。
bin/rails generate model User name:string
bin/rails generate migration AddUserToArticles user:references
bin/rails db:migrate
class User < ApplicationRecord
has_many :articles, dependent: :destroy
end
class Article < ApplicationRecord
belongs_to :user
enduser = User.create!(name: "Mity")
article = user.articles.create!(title: "関連付け", body: "本文")
article.user.name
# => "Mity"belongs_to側には通常外部キーが必要です。関連付けは便利ですが、削除時の影響も確認してdependentの方針を決めます。
バリデーションとエラー
class Article < ApplicationRecord
validates :title, presence: true
validates :body, presence: true
validates :title, length: { maximum: 100 }
end
article = Article.new(title: "")
article.valid?
# => false
article.errors[:title]
# => ["can't be blank"]バリデーションは利用者に見せる入力エラーを作る仕組みです。データベース側の制約も必要な場面があるため、重要な一意性や必須条件はアプリケーションとDBの両面から検討します。
scopeと再利用できる検索
同じ検索条件を何度も使うならscopeへ名前を付けます。検索条件をモデルへまとめることで、コントローラーにSQLの細部が増えるのを防げます。
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
scope :with_keyword, ->(word) { where("title LIKE ?", "%#{word}%") }
endArticle.published.recent.limit(5)
Article.published.with_keyword("Rails").pluck(:title)["Rails入門", "MVCを理解する"]scopeはRelationを返すため、条件をつなげられます。引数を受け取るscopeでは、検索値をSQL文字列へ直接連結せず、プレースホルダーへ渡します。
includesでN+1を避ける
記事一覧で各記事の著者名を表示するとき、記事を1回取得したあと著者を記事ごとに取得するN+1クエリが起きることがあります。関連データを先に読み込むにはincludesを使います。
articles = Article.limit(10)
articles.each { |article| puts article.user.name }
# Article 1回 + User 10回になる可能性articles = Article.includes(:user).limit(10)
articles.each { |article| puts article.user.name }
# Article 1回 + User 1回を目指せるbin/rails db:prepare
bin/rails test
# development.logのSQL回数も確認する常にincludesを付ければよいわけではありません。画面で使う関連を把握し、ログやテストでクエリ数を確認してから改善します。
コールバックとenum
bin/rails generate migration AddStatusToArticles status:integer
bin/rails db:migrateコールバックは保存前後など決まったタイミングで処理します。enumは状態を名前で扱う仕組みです。どちらも便利ですが、処理を隠しすぎると保存時の副作用が分かりにくくなるため、短い処理に限定します。
class Article < ApplicationRecord
enum :status, { draft: 0, live: 1, archived: 2 }
before_validation :set_default_status, on: :create
private
def set_default_status
self.status ||= :draft
end
endarticle = Article.create!(title: "下書き", body: "本文")
article.draft?
# => true
article.live!
Article.live.count
# => 1状態の変更が重要な業務処理を呼ぶ場合は、コールバックへ詰め込まず、名前付きのサービスや明示的なメソッドとして表す方法も検討します。
練習問題
問題:公開済みの記事を3件まで取得する
公開済みで、作成日の新しい順に3件だけ取得してください。
articles = Article.where(published: true)
.order(created_at: :desc)
.limit(3)