Ruby on Railsガイドを通読してまとめる Part.1
なかなかボリューミーなことで有名なRuby on Railsガイド。
実務未経験、Rails触り初めて4ヶ月の人間が気になった所だけ抜き出ししてまとめます。 基本的に、「理解できなかった所」や「大事だなと思った所」を中心にしてまとめようと思います。
はじめに
Rails をはじめよう
ネストさせたモデルに値を送る(7.2)
このコードの1行目が初見だったので掲載。
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %> <p> <%= form.label :commenter %><br> <%= form.text_field :commenter %> </p> <p> <%= form.label :body %><br> <%= form.text_area :body %> </p> <p> <%= form.submit %> </p> <% end %>
大まかな挙動はわかるけど、体系的にはどう動くのかなって思ってたらスタックオーバーフローで質問がありました。
Ruby on Rails Form For with two parameters - Stack Overflow
ネストさせたモデルに値を送りたいときの書き方のようですね。(実際の挙動見てないので、導入するときは要確認) 結構色々なところで頻出してそうなのに初対面だったのは驚き。@articleはArticle.find(params[:id])等してキャッチしてるのが前提です。
Basic認証(9.1)
検証環境のサーバー等で制限をかけたい場合によく使われる基本的な認証の1つ、BASIC認証。 Railsでの実装方法は以下の通り。
class ArticlesController < ApplicationController http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] def index @articles = Article.all end # (以下省略)
http_basic_authenticate_withメソッドにこのように引数を与えてあげれば、「Articleコントローラーのindexとshowアクション以外でBasic認証必要」という処理になります。
Qiitaに、アプリケーション全体に設定してる方がいました。環境変数でnameとpasswordを決めておいて、ソースコードで見れないようにしてますね。こっちのほうがより安全そうです。
RailsでBasic認証の導入 〜完全版〜 - Qiita
モデル
Active Record の基礎
命名ルール(2.1)
モデル名が単数形、テーブル名が複数形という慣習があるけど、英単語によってはただ「s」をつければ複数形になるわけではないので注意が必要。
モデル名→ テーブル名 Mouse → mice Person → people
この挙動はActiveSupportによって、Stringが拡張されて#pluralizeメソッドを貰うことで保証しているようです。
[1] pry(main)> require 'active_support' => true [2] pry(main)> require 'active_support/core_ext' => true [3] pry(main)> 'cat'.pluralize => "cats" [4] pry(main)> 'person'.pluralize => "people"
(require 'active_support' だけじゃNoMethodError出たので、'active_support/core_ext'もrequireしました。参考記事:Qiita:https://qiita.com/seri_k/items/4818af527bd0e94cc860)
スキーマのルール(2.2)
テーブルのカラム名について、created_atなどは予約語なので独自に定めるべきではない。他の予約語として、以下のものがある。
- 楽観的ロックのための lock_versionカラム。
- 単一継承テーブルのための typeカラム。
- ポリモーフィック関連のための「関連付け名_typeカラム」
- 親が持つ子の数をキャッシュしておくための「テーブル名_countカラム」
これらはRailsルールがあるので、意図せずカラム名にしないよう注意。
命名ルールを上書きする(4)
以下のように記述することで、命名ルールを上書きできる。
class Product < ApplicationRecord self.table_name = "PRODUCT" end
class Product < ApplicationRecord self.primary_key = "product_id" end
Active Record マイグレーション
マイグレーションの概要(1)
マイグレーションがエラーを吐いたさい、全てがロールバックされるとは限らないので注意が必要。
(引用)スキーマ変更のステートメントを利用できるトランザクションがデータベースでサポートされている場合、マイグレーションはトランザクションの内側にラップされて実行されます。これらがデータベースでサポートされていない場合は、マイグレーション中に一部が失敗した場合にロールバックされません。その場合は、変更を手動でロールバックする必要があります。
ん、これって…どこかで聞いたような…
MySQLの闇
— fujiten (@wonder_meet) 2019年3月6日
>Some statements cannot be rolled back. In general, these include data definition language (DDL) statementshttps://t.co/sY9Ekc28bV
RailsでのMySQLの話だけど、マイグレーションファイル実行したのにDBに一部カラムだけ追加されたのちエラー吐かれてDBがどことも整合性の取れない闇へと落ちる挙動、たぶん仕様っぽくて恐怖を感じる
— fujiten (@wonder_meet) 2019年3月6日
ワイや!
スキーマ変更(カラム追加等)のDDL文がエラー吐いたらこうなるのはやはり一部RDBMSでは仕様のようです。 おとなしく手動でロールバックしましょう!
テーブルを作成する(3.1)
テーブルを作るさい:commentオプションというものがあるらしい。初めて見た。 db/schema.rbを直接見るか、DB用のGUIツールを使えば確認できるそうです。 使い方としては、「nameってカラム名だけど何のことだっけ?」みたいなものに、注釈を付けておける、といった効果がある。
案外使いやすそう?
class CreatePatients < ActiveRecord::Migration[5.0] def change create_table :patients do |t| t.string :name, comment: "患者のニックネーム" t.string :first_name, comment: "患者の下の名前" t.string :last_name, comment: "患者の名字" t.timestamps end end end
参考記事: [Rails 5] マイグレーション時にデータベースのカラムにコメントを追加する|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社
テーブル結合を作成する(3.2)
中間テーブルをサクッと作りたいあなたへ。
create_join_table :products, :categories
productテーブルとcategoryテーブルの中間テーブル(外部キー保証)が出来ます。 インデックスや、独自のテーブル名を設定することも可能。詳細はRailsガイドへどうぞ。
テーブルを変更する(3.3)
完全に知らなかったので全て引用してます。
既存のテーブルを変更するchange_tableは、create_tableとよく似ています。基本的にはcreate_tableと同じ要領で使いますが、ブロックに対してyieldされるオブジェクトではいくつかのテクニックが利用できます。たとえば次のようになります。
change_table :products do |t| t.remove :description, :name t.string :part_number t.index :part_number t.rename :upccode, :upc_code end
上のマイグレーションではdescriptionとnameカラムが削除され、stringカラムであるpart_numberが作成されてインデックスがそこに追加されます。そして最後にupccodeカラムをリネームしています。
ただ、ここまで一気にテーブル構造変えるのは保守性落ちそうですね。 作り直しがどうしても出来ない場合の策か。
データベースをリセットする(4.3)
bin/rails db:resetは一度テーブルをドロップしたあと、db/schema.rbに従って再びテーブルを作成しているだけで、マイグレーションをもう一度実行しているわけではないらしい。知らなかった。(db/schema.rbをスキーマダンプというらしい。後で説明がある)
既存のマイグレーションを変更する(5)
テーブルのカラム名などを変更したいときに、既存のマイグレーションファイルに手を加えて再実行するのはNG。コミットしてマージされてしまったマイグレーションファイルに対する変更をしてしまうとチームメンバーに影響がある。
新しいマイグレーションファイルを作成して対応しましょう。
スキーマダンプの種類(6.2)
db/schema.rbとして保存されているものを「スキーマダンプ」と呼ぶ。これは現在のDBの状態をダンプ(出力)したもの。 設定次第でdb/structure.sqlというsql文で書かれたスキーマダンプを手に入れることも出来る。(DBの高度な機能を使うためにはそうした方がいい。)
マイグレーションファイルとスキーマダンプとの関係を表す参考記事置いてあったので、貼っておきます。 schema.rbとmigrationファイルの関係をまとめてみたよ - Qiita
ActiveRecordバリデーション
バリデーション(1.1)
データをデータベースに保存する前に行えるバリデーションの種類は大別すると4つである。
クライアントサイド(JS等)でのバリデーション
- バリデーションとしての信頼性は低いが、ユーザーへの即座なフィードバックが可能である。モダンなアプリとかだったら、「そのパスワードの強度は低いですよ」みたいなのをリアクティブに教えてくれるものが多い気がする。もちろん、JSでの実装。
コントローラーレベルでのバリデーション
- 非推奨。テストと保守が困難になるため、避けたほうがいい。
モデルレベル
- 最も重要。さまざまなバリデーションが元々アクティブレコードに用意されている。また、自分でカスタムしたものを使うことも可能。テスト・メンテナンスも容易。バリデーションを行いたいときの基本である。
データベースレベル
- テストや保守を行うのは難しくなるが、Rails以外のアプリケーションからそのDBにアクセスするようなことがあるのなら、DB自体にバリデーションを付与しておくのは良い方法である。また、ユニークネス制約など、他の層のバリデーションでは実装が難しく、かつ使用頻度が高いものもあるので、覚えておこう。
モデル層での色々なバリデーション
acceptance(2.1)
class User < ActiveRecord::Base validates_acceptance_of :terms_of_service end > user = User.new(terms_of_service: true) > user.valid? #=> true > user.terms_of_service = '1' > user.valid? #=> true
validates_acceptance_ofバリデーションは、利用規約同意のチェックボックス等に使える。 チェックボックスがチェックされて、その値が「true」か「"1"」なら通る(試してないけどInteger型の「1」でも通りそう) DBに存在していないカラムについても弾ける。「利用規約に同意しているかどうかカラム」なんて作ったところで、全部「true」に決まってるので、モデル層で弾く設計にした方が良いということでこのバリデーションは良さげですね。
参考記事: Rails 5 accepts 1 or true for acceptance validation | BigBinary Blog
confirmation(2.3)
パスワード確認用に使えるバリデーション。
class Person < ApplicationRecord validates :email, confirmation: true validates :email_confirmation, presence: true #2行目のバリデーションを働かせるためnil不許可 end
これでビューテンプレートに「email」と「email_confirmation」に関するフィールドを作ればOK。同じ値であることを保証する。
format(2.5)
withオプションで与えられた正規表現とマッチするものだけを通す。
class Product < ApplicationRecord validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, message: "英文字のみが使えます" } end
メールアドレスのバリデーションでよく出てくる気がする。
uniquness(2.11)
モデル側で定める一意性制約。どうやって挙動を保証してるのかな〜と思ったらSQLクエリを発行しているようですね。まあ、DB見ないとユニークネスかどうかわからないのは当たり前か。(もしかして何か裏技あるのかと思ってた)
validates_with(2.12)
カスタムバリデーションを使う際の記載。
class GoodnessValidator < ActiveModel::Validator def validate(record) if options[:fields].any?{|field| record.send(field) == "Evil" } record.errors[:base] << "これは悪人だ" end end end class Person < ApplicationRecord validates_with GoodnessValidator, fields: [:first_name, :last_name] end
ちょっと複雑なコード。 言葉にしてみると、「Personモデルのインスタンスが保存されるさい、GoodnessValidatorにバリデーション処理が移譲される。移譲先ではvalidateメソッドが打たれることが保証され、引数としてインスタンス自身(record)が渡される。」ということ。
if options[:fields].any?{|field| record.send(field) == "Evil" }
この部分は、「もしオプションにfiledsという項目があったなら、そのfieldsという項目の数だけ、後ろのブロック{ record.send(field) =="Evil" }を評価し、その戻り値が結果とせよ。」という感じかな。record.send(field)は、recordにfield(:first_nameとか)のメソッドを打てということ。
ここ解説少なかったので掘り下げました。
(補足)
sendの挙動
[1] pry(main)> str = "str" => "str" [2] pry(main)> str.send(:upcase) => "STR"
validates_each(2.13)
カスタムバリデーションを使うほどではないけど、既存のバリデーションじゃ不十分なときに使う。
class Person < ApplicationRecord validates_each :name, :surname do |record, attr, value| record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ end end
属性ごとに後ろのブロックを評価。ブロック引数として、「インスタンス自身(record)」、「インスタンスの属性名(attr)」、「属性の値(value)」が固定で渡される。順番大事。
:ifや:unlessでバリデージョンの前に呼び出すメソッドを指定
一番良く使うオプションらしい。
class Order < ApplicationRecord validates :card_number, presence: true, if: :paid_with_card? def paid_with_card? payment_type == "card" end end
支払い方法がカードのときのみカードナンバーを入力させたいときは上のような書き方。 if:にProcオブジェクトを渡すことも可能。そうすると、そのオブジェクトの処理はバリデーションの前に呼び出されて評価される(ブロックの評価値はtrueかfalseにしておく。)
カスタムバリデータ(6.1)
個別の要素を検証したいときは、カスタムバリデータに「ActiveModel::EachValidator」を継承させるのが一番手っ取り早い。
(2.12で示したように、「ActiveModel::Validator」を継承させたあと、処理を委譲させてもよい。)
class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i record.errors[attribute] << (options[:message] || "はメールアドレスではありません") end end end class Person < ApplicationRecord validates :email, presence: true, email: true end
呼び出す側のモデルで「validates :email, email:true」とするだけで、簡単にカスタムバリデータさんを呼び出せる。
バリデーションエラーについて
valid?だったりsaveを打ったオブジェクトはActiveModel::Errorsクラスのインスタンスを一つ手に入れる。それはrecord.errorsで参照できる。
record.errorsに打てるメソッドとしては、[:attribute]、add、size、full_messages等…
詳しくはAPIガイドへ!
感想
思ってたより読みやすい…すげえよ公式ガイド…。
「どういう挙動をするか」というコード例が助かります。
あと解説も想像してたよりは丁寧でありがたい(とはいえ、Rails学んだばかりの人だと難しいと思う。)
基礎部分が15記事あって、今回4つまとめたからPart.4までは続けようと思います。
Rails勉強してる方は、一度読んでみて「理解できるな」と思ったら読んでみると良いと思います!(ただ、作りながら覚える形式じゃないため、挙動を想像できない場合は非推奨かも。)