Open9

Railsについて

okita kamegorookita kamegoro

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アプリケーションにおけるモデルの構築において異なる役割を担う。前者はデータベーステーブルに直接対応するモデルに用いられ、後者はテーブルに直接対応しないがモデルとしての振る舞いを必要とするオブジェクトに利用される。

okita kamegorookita kamegoro

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に任せて良いと思う

okita kamegorookita kamegoro

セキュリティーに関する実装

https://guides.rubyonrails.org/security.html#environmental-security

外部連携の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管理に載せることで、秘密鍵とコードが一緒にデプロイされるためアトミックデプロイなどにも対応しやすくなる。

okita kamegorookita kamegoro

個人的にモヤっとすること

RAILS_ENV の管理について

下記のように本番でconsoleを叩いて環境を確認しようとした際に、結果が false になっていることがあった。
.envapplication.yml では RAILS_ENV が指定されておらず、デフォルトで developmentになることから結果が falseになっているようだった。

Rails.env.production?

よくよくプロジェクトを見ると、 プロセス自体はpuma で起動しており、その中で production に上書きするように指定があった。

これがデフォのやり方かわからないが、暗黙的なやり方のように思えてあまり好ましくない。
もし環境を変えたい場合は明示的に RAILS_ENV=production bin/rails c のように指定すればよくそれ位がは、どのように立ち上げても必ず同じ結果にならなければ混乱を招く原因となる。

これがRailsのデフォの方針であれば必ず直してもらいたいし、それがいいのかはちょっとモヤっとする

okita kamegorookita kamegoro

気軽に環境変数を上書きしてはいけない理由

PHP, Rubyでは基本的に1リクエスト1プロセスとなり環境変数を変更したとしても他のリクエストに影響を及ぼす可能性は高くはない。

このため特別なケースにおいてリクエストのミドルウェアなどで環境変数を変更してその後の後続する処理で利用するような使い方の実装を行なった時があった。

ただその後、RspecをCIで実行するとどうしても落ちてしまう。調査を進めるとmiddleware/controllerでの環境変数の変更が後続するテストに引き継がれた結果予期せぬエラーを生じさせていた。

Rspecでテストごとに環境変数をリセットする処理を書くか、そもそも環境変数を上書きするような設計は見直すべきかもしれない。

Rspecの場合はenvをstubする手もある
https://shinkufencer.hateblo.jp/entry/2018/09/27/235855
https://spirits.appirits.com/doruby/9473/

okita kamegorookita kamegoro

capybaraのhave_contentを利用するときの注意点

 Ambiguous match, found 2 elements matching visible xpath "/html"

以下のエラーが発生しているときは、HTMLタグが重複して出力されているか、HTMLタグの外側にDOMが存在する時になるので注意

okita kamegorookita kamegoro

Specで環境変数をstubしたにも関わらず、Modelで参照できない場合

Rails Model Loading in Test Environment

  1. モデルの事前ロード:

    • テスト環境では、通常、すべてのモデルが事前にロードされます。これは、テストの実行速度を向上させ、定義の依存関係の問題を回避するためです。
  2. 定数の自動読み込み:

    • Railsは定数の自動読み込み機能を持っています。クラスや定数が参照された時点で、対応するファイルが読み込まれます。
  3. テストファイルの実行順序:

    1. RSpecの環境が設定される
    2. rails_helperspec_helperが読み込まれる
    3. テストに必要なファイルが読み込まれる(この時点でモデルも読み込まれる)
    4. 個々のテストファイルが実行される
  4. binding.breakの影響:

    • モデルファイル内のbinding.breakは、そのモデルが最初に読み込まれる時点で実行されます。
    • これは通常、テストの実際の実行よりも前に発生します。
  5. 環境変数とモックの影響:

    • テストファイル内で設定されたallow(ENV).to receive(...)は、モデルが既に読み込まれた後に実行されます。
    • そのため、モデル内のコードは本来の環境変数の値を見ることになります。
  6. 解決策:

    • モデル内での環境変数チェックを遅延評価する
    • テスト全体で一貫した環境変数の設定を使用する
    • モデルの読み込みを制御する(必要な場合)
  7. 推奨アプローチ:

    # 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
    

これらの点を考慮することで、テスト環境でのモデルのロードと環境変数の扱いをより適切に管理できます。