Railsについて
ActiveRecord::BaseとActiveModel::Modelの違いとバリデーションメッセージのi18n
はじめに
Railsフレームワークは、Ruby言語でWebアプリケーションを開発するためのMVC(モデル-ビュー-コントローラー)フレームワークである。モデルはアプリケーションのビジネスロジックとデータの管理を担い、その中心的な役割を果たす。この文脈で、ActiveRecordとActiveModelの2つのモジュールがRailsにおけるモデルの構築に用いられる。
ActiveRecord::Baseについて
ActiveRecord::BaseはRailsのORM(Object-Relational Mapping)ライブラリであり、Rubyオブジェクトとデータベーステーブル間のマッピングを提供する。これにより、データベース操作を抽象化し、SQLクエリを直接書くことなくデータの作成、読み出し、更新、削除(CRUD操作)を行うことができる。さらに、バリデーション、リレーションシップ、トランザクションなどの高度な機能もサポートしている。
ActiveModel::Modelについて
ActiveModel::Modelは、テーブルに直接対応しないオブジェクトでモデルとしての振る舞いを必要とする場合に利用される。これをincludeすることで、オブジェクトはActiveModelのAPIを利用できるようになり、特にバリデーションやi18nなどの機能が利用可能になる。フォームオブジェクトやサービスオブジェクトとして活用されることが多い。
バリデーションメッセージのi18n
Railsアプリケーションでは、ユーザーに向けたエラーメッセージを含むあらゆるテキストを、アプリケーションの言語設定に基づいて表示することが望ましい。これにはactiverecord
およびactivemodel
キーを使用したロケールファイルの設定が役立つ。activerecord
キーはActiveRecordベースのモデル、activemodel
キーはActiveModelを使用するモデルの設定に用いられる。
ActiveModelを用いた場合のバリデーションエラーメッセージのi18n設定
例えば、以下のようにmodel.ja.yml
にバリデーションメッセージのi18n設定を行う。
ja:
activemodel:
models:
update_review_usecase: ""
attributes:
update_review_usecase:
comment: コメント
この設定により、UpdateReviewUsecase
クラス内でバリデーションエラーが発生した際には、日本語でカスタマイズされたエラーメッセージが表示されるようになる。
まとめ
ActiveRecord::BaseとActiveModel::Modelは、Railsアプリケーションにおけるモデルの構築において異なる役割を担う。前者はデータベーステーブルに直接対応するモデルに用いられ、後者はテーブルに直接対応しないがモデルとしての振る舞いを必要とするオブジェクトに利用される。
rubocopの導入について
これまでrubocopで管理されていない際に導入する場合の手引き
いきなりrubocopの修正まで入れると辛いので、とりあえずrubocopを入れてciが回る状態にする。
rubocopのエラーは全てignoreを行い、今後地道に解消していく。
Rubocop周りのGem導入
gem 'guard-rubocop', require: false
gem 'rubocop', require: false
gem 'rubocop-faker'
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
.rubocop.yml と rubocop_todo.ymlの生成
$ bundle exec rubocop init
また、specへのrubocopは緩めた方が良いのでrubocop.ymlでexcludeするように修正した上で、baselineを引く。
以下、rubocop_todo.ymlの生成
bundle exec rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 999999
# --auto-gen-config: rubocop_todo.ymlの生成
# --auto-gen-only-exclude: excludeのみを出力し、Maxの緩和を行わない(これつけないとガバガバになる)
# --exclude-limit: ignoreされるかずが多いとエラーを吐くので大きい値に変更
rubocopの設定について
ここはチーム内で議論。metricsは無闇に変えないこと。
細かいチューニング・今後の方針
rubocop_todo.yml
をjsonに変換して
data = {} // jsonに変換したものを突っ込む
Object.fromEntries(Object.keys(data).map( i => [i, data[i].Exclude?.length ?? 0]).sort(function(a, b) {
return b[1] - a[1];
}))
これで多いエラーを確認できる。
そこから多い順に撲滅していく。
あとは、影響が少ないようなStyle系を優先的に撲滅するなど決めて地道に解消していけば良いかと思う。
また、エラーの数が1桁のものはtodo自体を外して、autocorrectに任せて良いと思う
セキュリティーに関する実装
外部連携のAPI key や secret keyを.envやapplicatioin.ymlに記載しがちだが、本当にそれでいいのだろうか?
Rails の Credentialsの機能を用いて暗号化した状態で保存することも可能なのでその方法はどうか?
使い方
# vi で起動するように指定。自分の手元ではvscodeではうまく保存ができなかった
EDITOR="vi" bin/rails credentials:edit -e development
# Adding config/credentials/development.key to store the encryption key: hogehoge*********hoge
#
# Save this in a password manager your team can access.
#
# If you lose the key, no one, including you, can access anything encrypted with it.
#
# create config/credentials/development.key
#
# Ignoring config/credentials/development.key so it won't end up in Git history:
#
# append .gitignore
#
# File encrypted and saved.
これによりエディタが立ち上がり、 development.key
, development.yml.enc
が生成される。
development.key
は秘密鍵になるので gitignoreしなければならない。これも自動で行われるはずだが、commitする際は注意する。
*.key
は開発環境ないしは、本番に配置して参照(複合化)できるようにし、 *.yml.enc
はgit管理を行うことでセキュアながらも管理ができる状態にするのがこのCredentialsのコンセプトなのだと思う。
また上記のようにgit管理に載せることで、秘密鍵とコードが一緒にデプロイされるためアトミックデプロイなどにも対応しやすくなる。
個人的にモヤっとすること
RAILS_ENV の管理について
下記のように本番でconsoleを叩いて環境を確認しようとした際に、結果が false
になっていることがあった。
.env
や application.yml
では RAILS_ENV
が指定されておらず、デフォルトで development
になることから結果が false
になっているようだった。
Rails.env.production?
よくよくプロジェクトを見ると、 プロセス自体はpuma
で起動しており、その中で production
に上書きするように指定があった。
これがデフォのやり方かわからないが、暗黙的なやり方のように思えてあまり好ましくない。
もし環境を変えたい場合は明示的に RAILS_ENV=production bin/rails c
のように指定すればよくそれ位がは、どのように立ち上げても必ず同じ結果にならなければ混乱を招く原因となる。
これがRailsのデフォの方針であれば必ず直してもらいたいし、それがいいのかはちょっとモヤっとする
Rspec テストについて
気軽に環境変数を上書きしてはいけない理由
PHP, Rubyでは基本的に1リクエスト1プロセスとなり環境変数を変更したとしても他のリクエストに影響を及ぼす可能性は高くはない。
このため特別なケースにおいてリクエストのミドルウェアなどで環境変数を変更してその後の後続する処理で利用するような使い方の実装を行なった時があった。
ただその後、RspecをCIで実行するとどうしても落ちてしまう。調査を進めるとmiddleware/controllerでの環境変数の変更が後続するテストに引き継がれた結果予期せぬエラーを生じさせていた。
Rspecでテストごとに環境変数をリセットする処理を書くか、そもそも環境変数を上書きするような設計は見直すべきかもしれない。
Rspecの場合はenvをstubする手もある
capybaraのhave_contentを利用するときの注意点
Ambiguous match, found 2 elements matching visible xpath "/html"
以下のエラーが発生しているときは、HTMLタグが重複して出力されているか、HTMLタグの外側にDOMが存在する時になるので注意
地味に理解していなかったけど、モデルに紐づける場合はヘルパーを利用するのがいい
Specで環境変数をstubしたにも関わらず、Modelで参照できない場合
Rails Model Loading in Test Environment
-
モデルの事前ロード:
- テスト環境では、通常、すべてのモデルが事前にロードされます。これは、テストの実行速度を向上させ、定義の依存関係の問題を回避するためです。
-
定数の自動読み込み:
- Railsは定数の自動読み込み機能を持っています。クラスや定数が参照された時点で、対応するファイルが読み込まれます。
-
テストファイルの実行順序:
- RSpecの環境が設定される
-
rails_helper
やspec_helper
が読み込まれる - テストに必要なファイルが読み込まれる(この時点でモデルも読み込まれる)
- 個々のテストファイルが実行される
-
binding.break
の影響:- モデルファイル内の
binding.break
は、そのモデルが最初に読み込まれる時点で実行されます。 - これは通常、テストの実際の実行よりも前に発生します。
- モデルファイル内の
-
環境変数とモックの影響:
- テストファイル内で設定された
allow(ENV).to receive(...)
は、モデルが既に読み込まれた後に実行されます。 - そのため、モデル内のコードは本来の環境変数の値を見ることになります。
- テストファイル内で設定された
-
解決策:
- モデル内での環境変数チェックを遅延評価する
- テスト全体で一貫した環境変数の設定を使用する
- モデルの読み込みを制御する(必要な場合)
-
推奨アプローチ:
# In your model def self.v2? @v2 ||= ENV['V2'] == 'true' end # Use this method instead of directly checking ENV['V2']
# In your spec_helper.rb or rails_helper.rb RSpec.configure do |config| config.before(:suite) do ENV['V2'] = 'true' end config.after(:suite) do ENV.delete('V2') end end
これらの点を考慮することで、テスト環境でのモデルのロードと環境変数の扱いをより適切に管理できます。