※ 時間がなかったので、プロットだけ書いたものを ChatGPT に書かせただけになっているため、サンプルコードがちゃんと動かないなどあるかもしれません(随時直します)
はじめに
Ruby on Rails(以下、Rails)は「規約より設定(Convention over Configuration)」を重視するフレームワークです。そのため、書きやすく、理解しやすく、楽しい ことを目指しています。しかし、他の言語やフレームワークの経験者にとっては、独特な点が多く、混乱しやすい 側面もあります。
本記事では、特に混乱しやすいポイントにフォーカスし、それぞれの特徴を取り上げていきます。
メソッド名の ?
と !
これらは形式的なことなのでわかってしまえば簡単かもしれません。演算子ではなくて、名前の一部で使うことができ、ついている場合の意味が慣習として決まっています。
?
で終わるメソッド
意味: 真偽値を返すメソッドであることを表す(ように実装します)。
例
user.admin? # => true または false
order.completed? # => true または false
- ポイント:
?
は 構文ではなくメソッド名の一部 なので、自由に定義できる。true
/false
以外の値(例えばnil
)を返しても問題なく動作することがある。
!
で終わるメソッド
意味: より危険な操作を行う、または破壊的変更(mutate)を行うメソッド。また、失敗時に例外を投げることを示す意図の時もある。
例
user.save! # 保存に失敗した場合、例外を発生
name.upcase! # 変数 `name` の値を直接変更
- ポイント:
!
も 構文ではなくメソッド名の一部。save
とsave!
の違い:save
はfalse
を返す。save!
は失敗時に例外を発生させる。
map!
などの破壊的メソッドとsave!
のようなメソッドの違いに注意が必要。
モデルの has_many
/ belongs_to
による自動生成メソッドと ActiveRecord::Relation
Rails の has_many
や belongs_to
を宣言すると、自動的に 便利なメソッドが追加される。
特に has_many
で生成される関連オブジェクトは 単なる配列ではなく ActiveRecord::Relation
であり、データベースに対するクエリが可能であることを理解しておく必要がある。
has_many
によって追加されるメソッド
以下のように has_many :posts
を定義すると、user.posts
に対して次のメソッドが利用できる。
class User < ApplicationRecord has_many :posts end
メソッド | 説明 |
---|---|
user.posts.new(attributes) |
Post.new(attributes) と同じだが、user_id を自動設定 |
user.posts.build(attributes) |
new のエイリアス |
user.posts.create(attributes) |
Post.new(attributes).save を実行(失敗時はエラーを持つインスタンスを返す) |
user.posts.create!(attributes) |
Post.new(attributes).save! を実行(失敗時は例外を発生) |
ActiveRecord::Relation
と where
による絞り込み
has_many
の関連オブジェクトは 単なる配列ではなく ActiveRecord::Relation
なので、データベースに直接クエリを投げることができる。これによって、検索のコードがスッキリし、想定外の検索を防ぐ効果もある(例えば user.posts ならば、 user_id で絞り込みが入っている前提で考えられるので、別のユーザーの情報は混ざらないことがすぐに推察可能)。
user = User.first puts user.posts.class # => ActiveRecord::Relation
上記のように、型としては ActiveRecord::Relation か、それと同様に扱えるいくつかの型で取得できる。そして以下のように続けて検索条件を書ける。
user.posts.where(published: true) # WHERE published = true user.posts.order(created_at: :desc).limit(5) # ORDER BY created_at DESC LIMIT 5 user.posts.where(title: "特定のタイトル")
belongs_to
によって追加されるメソッド
belongs_to :user
を定義すると、post.user
に対して以下のメソッドが利用できる。
class Post < ApplicationRecord belongs_to :user end
メソッド | 説明 |
---|---|
post.build_user(attributes) |
User.new(attributes) を作成し、post.user_id を自動設定 |
post.create_user(attributes) |
User.new(attributes).save を実行(失敗時はエラーを持つインスタンスを返す) |
post.create_user!(attributes) |
User.new(attributes).save! を実行(失敗時は例外を発生) |
スコープ(scope)の活用
Rails では モデルの scope
を使うことで、データベースクエリを簡潔に記述できる。
class Post < ApplicationRecord scope :published, -> { where(published: true) } scope :recent, -> { order(created_at: :desc) } end
スコープの利用
Post.published # => 公開済みの投稿を取得 Post.recent # => 最新の投稿を取得 Post.published.recent # => 公開済みの最新の投稿を取得
スコープの注意点
scope
は ActiveRecord::Relation を返す ため、メソッドチェーンが可能。- 引数を受け取るスコープも定義可能。
class Post < ApplicationRecord scope :by_author, ->(author_id) { where(author_id: author_id) } end Post.by_author(1) # => author_id が 1 の投稿を取得
enum
の活用
Rails の enum
は整数値を意味のあるシンボルにマッピングするための便利な機能。
class Order < ApplicationRecord enum status: { pending: 0, shipped: 1, delivered: 2, canceled: 3 } end
enum
によるメソッドの自動生成
enum
を定義すると、以下のようなメソッドが自動的に利用可能になる。
order = Order.new(status: :pending) order.pending? # => true order.shipped? # => false order.shipped! order.status # => "shipped"
enum
によるスコープの自動追加
enum
を定義すると、各ステータスに対応するスコープも自動的に作成される。
Order.pending # => status が "pending" のレコードを取得 Order.shipped # => status が "shipped" のレコードを取得 Order.delivered # => status が "delivered" のレコードを取得
また、not_xxx
という形のスコープも利用可能。
Order.not_pending # => status が "pending" 以外のレコードを取得
enum
の注意点
enum
のキー(pending
,shipped
など)は シンボルで指定する。- データベースには整数値として保存されるため、変更時は マイグレーションの管理 に注意。
enum
の値を変更すると、既存のデータとの整合性が取れなくなる可能性がある。
Controller でよく行われるバリデーションのスタイル
Rails の scaffold
で生成される Controller では、バリデーションエラーが発生した場合、不完全なモデルをそのままビューに渡す という設計になっています。
class PostsController < ApplicationController def create @post = Post.new(post_params) if @post.save redirect_to @post, notice: '投稿が作成されました。' else render :new, status: :unprocessable_entity end end private def post_params params.require(:post).permit(:title, :content) end end
Post.new
を実行すると、モデルのインスタンスが作成されるが、この時点ではデータベースには保存されていない。save
を実行すると、バリデーションが適用され、成功すればデータベースに保存される。失敗した場合、モデルの errors
にバリデーションエラーの情報が格納される。
post = Post.new(title: "") post.valid? # => false post.save # => false post.errors.full_messages # => ["タイトルを入力してください"]
この create
アクションでは、@post.save
に失敗した場合、@post
(エラーを持つインスタンス)をそのまま new
ビューに渡している。ビュー側では @post.errors
を利用してエラーメッセージを表示できるため、ユーザーが入力内容を修正しやすくなっている。
このスタイルは バリデーションエラーが発生しても、ユーザーが入力した内容を保持できる という利点があります。