バギーレールコード:Rails開発者が犯す最も一般的な10の間違い
公開: 2022-03-11Ruby on Rails(「Rails」)は、Webアプリケーション開発プロセスの簡素化と合理化を目指すRubyプログラミング言語に基づく人気のあるオープンソースフレームワークです。
Railsは、設定より規約の原則に基づいて構築されています。 簡単に言えば、これは、デフォルトでは、Railsのエキスパート開発者が「標準」のベストプラクティスの規則(名前付け、コード構造など)に従うことを前提としており、そうすれば、「自動」で機能することを意味します。 -魔法のように」これらの詳細を指定する必要はありません。 このパラダイムには利点がありますが、落とし穴もあります。 最も注目すべきは、フレームワークの舞台裏で発生する「魔法」が、ヘッドフェイク、混乱、および「一体何が起こっているのか」につながる可能性があることです。 問題の種類。 また、セキュリティとパフォーマンスに関して望ましくない影響を与える可能性があります。
したがって、Railsは使いやすい一方で、誤用も難しくありません。 このチュートリアルでは、Railsの10の一般的な問題について、それらを回避する方法やそれらが引き起こす問題を含めて説明します。
よくある間違い#1:コントローラーにロジックを入れすぎる
RailsはMVCアーキテクチャに基づいています。 Railsコミュニティでは、ファットモデル、スキニーコントローラーについてしばらく話していましたが、私が継承した最近のRailsアプリケーションのいくつかはこの原則に違反していました。 ビューロジック(ヘルパーに格納する方が適切)またはドメイン/モデルロジックをコントローラーに移動するのは非常に簡単です。
問題は、コントローラーオブジェクトが単一責任の原則に違反し始め、コードベースへの将来の変更が困難になり、エラーが発生しやすくなることです。 一般に、コントローラーに必要なロジックのタイプは次のとおりです。
- セッションとCookieの処理。 これには、認証/承認または実行する必要のある追加のCookie処理も含まれる場合があります。
- モデルの選択。 リクエストから渡されたパラメータを指定して、適切なモデルオブジェクトを見つけるためのロジック。 理想的には、これは、後で応答をレンダリングするために使用されるインスタンス変数を設定する単一のfindメソッドの呼び出しである必要があります。
- パラメータ管理を要求します。 リクエストパラメータを収集し、適切なモデルメソッドを呼び出してそれらを永続化します。
- レンダリング/リダイレクト。 必要に応じて、結果(html、xml、jsonなど)をレンダリングするか、リダイレクトします。
これは依然として単一責任の原則の限界を押し広げますが、Railsフレームワークがコントローラーに必要とする最低限のことです。
よくある間違い#2:ビューにロジックを入れすぎる
すぐに使用できるRailsテンプレートエンジンであるERBは、可変コンテンツのページを作成するための優れた方法です。 ただし、注意しないと、すぐにHTMLとRubyコードが混在する大きなファイルになってしまい、管理と保守が困難になる可能性があります。 これはまた、多くの繰り返しにつながる可能性のある領域であり、DRY(自分自身を繰り返さないでください)の原則の違反につながる可能性があります。
これは、さまざまな形で現れる可能性があります。 1つは、ビューでの条件付きロジックの乱用です。 簡単な例として、現在ログインしているユーザーを返すcurrent_user
メソッドを使用できる場合を考えてみます。 多くの場合、ビューファイルには次のような条件付きロジック構造があります。
<h3> Welcome, <% if current_user %> <%= current_user.name %> <% else %> Guest <% end %> </h3>
このような処理を行うためのより良い方法は、誰かがログインしているかどうかに関係なく、 current_user
によって返されるオブジェクトが常に設定されていること、およびビューで使用されているメソッドに適切な方法で応答することです(nullと呼ばれることもあります)。物体)。 たとえば、次のようにapp/controllers/application_controller
でcurrent_user
ヘルパーを定義できます。
require 'ostruct' helper_method :current_user def current_user @current_user ||= User.find session[:user_id] if session[:user_id] if @current_user @current_user else OpenStruct.new(name: 'Guest') end end
これにより、前のビューコード例を次の1行の単純なコードに置き換えることができます。
<h3>Welcome, <%= current_user.name -%></h3>
Railsの推奨される追加のベストプラクティスがいくつかあります。
- ビューレイアウトとパーシャルを適切に使用して、ページで繰り返されるものをカプセル化します。
- Draper gemのようなプレゼンター/デコレーターを使用して、ビュー構築ロジックをRubyオブジェクトにカプセル化します。 次に、このオブジェクトにメソッドを追加して、ビューコードに追加した論理演算を実行できます。
よくある間違い#3:モデルにロジックを入れすぎる
ビューとコントローラーのロジックを最小化するためのガイダンスを考えると、MVCアーキテクチャーに残された唯一の場所は、すべてのロジックをモデルに配置することになるでしょう。
まあ、完全ではありません。
多くのRails開発者は実際にこの間違いを犯し、 ActiveRecord
モデルクラスにすべてを貼り付けてしまい、単一責任の原則に違反するだけでなく、メンテナンスの悪夢でもあるmongoファイルにつながります。
電子メール通知の生成、外部サービスとのインターフェース、他のデータ形式への変換などの機能は、データベース内のデータの検索と永続化にすぎないActiveRecord
モデルの中心的な責任とはあまり関係がありません。
それで、ロジックがビューに含まれるべきではなく、コントローラーに含まれるべきではなく、モデルに含まれるべきではない場合、それでは、どこに行くべきでしょうか?
プレーンな古いRubyオブジェクト(PORO)を入力します。 Railsのような包括的なフレームワークを使用する場合、新しい開発者は、フレームワークの外部で独自のクラスを作成することを躊躇することがよくあります。 ただし、ロジックをモデルからPOROに移動することは、多くの場合、過度に複雑なモデルを回避するために医師が命じたものです。 POROを使用すると、電子メール通知やAPIインタラクションなどを、 ActiveRecord
モデルに固定するのではなく、独自のクラスにカプセル化できます。
したがって、それを念頭に置いて、一般的に言えば、モデルに残しておく必要がある唯一のロジックは次のとおりです。
-
ActiveRecord
の構成(つまり、リレーションと検証) - 少数の属性の更新とデータベースへの保存をカプセル化するための単純なミューテーションメソッド
- ラッパーにアクセスして、内部モデル情報を非表示にします(たとえば、データベースの
first_name
フィールドとlast_name
フィールドを組み合わせたfull_name
メソッド) - 洗練されたクエリ(つまり、単純な
find
よりも複雑です); 一般的に言えば、モデルクラス自体の外部でwhere
メソッドまたはそのような他のクエリ構築メソッドを使用しないでください。
よくある間違い#4:一般的なヘルパークラスをゴミ捨て場として使用する
この間違いは、実際には上記の3番目の間違いの当然の結果です。 説明したように、Railsフレームワークは、MVCフレームワークの名前付きコンポーネント(つまり、モデル、ビュー、コントローラー)に重点を置いています。 これらの各コンポーネントのクラスに属するものの種類についてはかなり良い定義がありますが、3つのいずれにも当てはまらないように見えるメソッドが必要になる場合があります。
Railsジェネレーターは、作成する新しいリソースごとに、ヘルパーディレクトリと新しいヘルパークラスを便利に構築します。 ただし、モデル、ビュー、またはコントローラーに正式に適合しない機能をこれらのヘルパークラスに詰め込み始めるのは非常に魅力的です。
Railsは確かにMVC中心ですが、独自のタイプのクラスを作成し、それらのクラスのコードを保持するための適切なディレクトリを追加することを妨げるものは何もありません。 追加の機能がある場合は、どのメソッドをグループ化するかを考え、それらのメソッドを保持するクラスの適切な名前を見つけます。 Railsのような包括的なフレームワークを使用することは、優れたオブジェクト指向設計のベストプラクティスを邪魔する言い訳にはなりません。
よくある間違い#5:あまりにも多くの宝石を使用する
RubyとRailsは、開発者が考えることができるほぼすべての機能を集合的に提供するgemの豊富なエコシステムによってサポートされています。 これは、複雑なアプリケーションをすばやく構築するのに最適ですが、アプリケーションのGemfile
内のgemの数が、提供される機能と比較して不釣り合いに多い、肥大化したアプリケーションも数多く見られます。
これにより、いくつかのRailsの問題が発生します。 宝石を過度に使用すると、Railsプロセスのサイズが必要以上に大きくなります。 これにより、本番環境のパフォーマンスが低下する可能性があります。 ユーザーの不満に加えて、これにより、より大きなサーバーメモリ構成が必要になり、運用コストが増加する可能性があります。 また、大規模なRailsアプリケーションの起動に時間がかかるため、開発が遅くなり、自動テストにかかる時間が長くなります(通常、遅いテストはそれほど頻繁には実行されません)。
アプリケーションに持ち込む各gemは、他のgemに依存している可能性があり、それらは他のgemに依存している可能性があることに注意してください。 したがって、他の宝石を追加すると、複合効果が生じる可能性があります。 たとえば、 rails_admin
gemを追加すると、合計11個のgemが追加され、基本のRailsインストールから10%以上増加します。
この記事の執筆時点で、Rails 4.1.0の新規インストールでは、 Gemfile.lock
ファイルに43個のgemが含まれています。 これは明らかにGemfile
に含まれている以上のものであり、少数の標準的なRailsgemが依存関係として持ち込むすべてのgemを表しています。
各宝石を追加するときに、余分なオーバーヘッドが価値があるかどうかを慎重に検討してください。 例として、開発者は、基本的にモデル構造に優れたWebフロントエンドを提供するため、 rails_admin
gemを何気なく追加することがよくありますが、実際には、洗練されたデータベースブラウジングツールにすぎません。 アプリケーションに追加の権限を持つ管理者ユーザーが必要な場合でも、生のデータベースアクセスを許可したくない場合は、このgemを追加するよりも、独自のより合理化された管理機能を開発する方がよいでしょう。
よくある間違い#6:ログファイルを無視する
ほとんどのRails開発者は、開発中および本番環境で使用可能なデフォルトのログファイルを認識していますが、それらのファイルの情報に十分な注意を払っていないことがよくあります。 多くのアプリケーションは、本番環境でHoneybadgerやNew Relicなどのログ監視ツールに依存していますが、アプリケーションの開発とテストのプロセス全体を通じてログファイルを監視することも重要です。
このチュートリアルで前述したように、Railsフレームワークは、特にモデルにおいて、多くの「魔法」を実行します。 モデルで関連付けを定義すると、リレーションを非常に簡単に取得して、ビューですべてを利用できるようになります。 モデルオブジェクトを埋めるために必要なすべてのSQLが生成されます。 それは素晴らしいことです。 しかし、生成されているSQLが効率的であることをどうやって知ることができますか?

よく遭遇する1つの例は、N+1クエリ問題と呼ばれます。 問題はよく理解されていますが、問題が発生していることを確認する唯一の実際の方法は、ログファイルのSQLクエリを確認することです。
たとえば、一般的なブログアプリケーションに次のクエリがあり、選択した一連の投稿に対するすべてのコメントを表示するとします。
def comments_for_top_three_posts posts = Post.limit(3) posts.flat_map do |post| post.comments.to_a end end
このメソッドを呼び出すリクエストのログファイルを見ると、次のようなものがあります。3つのpostオブジェクトを取得するために1つのクエリが実行され、次にそれらの各オブジェクトのコメントを取得するためにさらに3つのクエリが実行されます。
Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700 Processing by PostsController#some_comments as HTML Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (5.6ms) ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]] Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]] Comment Load (1.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 3]] Rendered posts/some_comments.html.erb within layouts/application (12.5ms) Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)
RailsでのActiveRecord
の積極的な読み込み機能により、読み込まれるすべての関連付けを事前に指定できるため、クエリの数を大幅に減らすことができます。 これは、構築中のArel( ActiveRecord::Relation
)オブジェクトでincludes
(またはpreload
)メソッドを呼び出すことによって行われます。 includes
を使用すると、 ActiveRecord
は、指定されたすべての関連付けが可能な限り最小のクエリ数を使用してロードされるようにします。 例えば:
def comments_for_top_three_posts posts = Post.includes(:comments).limit(3) posts.flat_map do |post| post.comments.to_a end end
上記の改訂されたコードを実行すると、ログファイルに、すべてのコメントが3つではなく1つのクエリで収集されたことがわかります。
Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700 Processing by PostsController#some_comments as HTML Post Load (0.5ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (4.4ms) SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3) Rendered posts/some_comments.html.erb within layouts/application (12.2ms) Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)
はるかに効率的です。
N + 1問題に対するこの解決策は、実際には、十分な注意を払っていない場合にアプリケーションの「内部」に存在する可能性がある種類の非効率性の例としてのみ意味されます。 ここでのポイントは、開発中に開発ログファイルとテストログファイルをチェックして、応答を構築するコードの非効率性をチェック(および対処)する必要があるということです。
ログファイルを確認することは、コードの非効率性を指摘し、アプリケーションが本番環境に移行する前にそれらを修正するための優れた方法です。 そうしないと、システムが稼働するまで、結果として生じるRailsのパフォーマンスの問題に気付かない可能性があります。これは、開発とテストで使用するデータセットが本番環境よりもはるかに小さい可能性があるためです。 新しいアプリで作業している場合は、本番データセットでさえ最初は小さく、アプリは正常に実行されているように見えます。 ただし、本番データセットが大きくなると、このようなRailsの問題により、アプリケーションの実行速度が低下します。
ログファイルが大量の情報で詰まっていることがわかった場合は、ここで必要のない情報をクリーンアップするために実行できることがいくつかあります(ここでの手法は、本番ログだけでなく開発でも機能します)。
よくある間違い#7:自動テストの欠如
RubyとRailsは、デフォルトで強力な自動テスト機能を提供します。 多くのRails開発者は、TDDおよびBDDスタイルを使用して非常に洗練されたテストを作成し、rspecやcucumberなどのgemを使用してさらに強力なテストフレームワークを利用します。
Railsアプリケーションに自動テストを追加するのは簡単ですが、以前に作成されたテストが文字通りなかった(またはせいぜいごくわずかな)プロジェクトを継承または参加したことに非常に不愉快に驚いています。開発チーム。 テストをどの程度包括的にするかについては多くの議論がありますが、すべてのアプリケーションに少なくともいくつかの自動テストが存在する必要があることは明らかです。
一般的な経験則として、コントローラーのアクションごとに少なくとも1つの高レベルの統合テストを作成する必要があります。 将来のある時点で、他のRails開発者はコードを拡張または変更したり、RubyまたはRailsバージョンをアップグレードしたりする可能性が高く、このテストフレームワークは、アプリケーションの基本機能が働く。 このアプローチの追加の利点は、将来の開発者に、アプリケーションによって提供される機能の完全なコレクションの明確な描写を提供することです。
よくある間違い#8:外部サービスへの呼び出しをブロックする
Railsサービスのサードパーティプロバイダーは通常、APIをラップするgemを介して、サービスをアプリケーションに非常に簡単に統合できます。 しかし、外部サービスが停止したり、実行が非常に遅くなったりした場合はどうなりますか?
リクエストの通常の処理中にRailsアプリケーションでこれらのサービスを直接呼び出すのではなく、これらの呼び出しのブロックを回避するには、可能な場合は、これらのサービスをある種のバックグラウンドジョブキューイングサービスに移動する必要があります。 この目的でRailsアプリケーションで使用される人気のあるgemには、次のものがあります。
- 遅れた仕事
- Resque
- Sidekiq
処理をバックグラウンドジョブキューに委任することが非現実的または実行不可能な場合は、外部サービスがダウンしたり問題が発生したりする場合の避けられない状況に対して、アプリケーションに十分なエラー処理とフェイルオーバーのプロビジョニングがあることを確認する必要があります。 。 また、外部サービスなしでアプリケーションをテストして(おそらく、アプリケーションがネットワークから接続されているサーバーを削除することによって)、予期しない結果が発生しないことを確認する必要があります。
よくある間違い#9:既存のデータベース移行と結婚する
Railsのデータベース移行メカニズムを使用すると、データベースのテーブルと行を自動的に追加および削除するための命令を作成できます。 これらの移行を含むファイルには順番に名前が付けられているため、最初からそれらを再生して、空のデータベースを本番環境と同じスキーマに移動できます。 したがって、これは、アプリケーションのデータベーススキーマに対するきめ細かい変更を管理し、Railsの問題を回避するための優れた方法です。
これはプロジェクトの開始時には確かにうまく機能しますが、時間が経つにつれて、データベースの作成プロセスにかなりの時間がかかる場合があり、移行が間違って配置されたり、順序が狂ったり、同じデータベースサーバーを使用する他のRailsアプリケーションから導入されたりすることがあります。
Railsは、 db/schema.rb
(デフォルト)というファイルに現在のスキーマの表現を作成します。このファイルは通常、データベースの移行が実行されるときに更新されます。 rake db:schema:dump
タスクを実行することにより、移行が存在しない場合でも、 schema.rb
ファイルを生成できます。 Railsでよくある間違いは、ソースリポジトリへの新しい移行をチェックすることですが、対応して更新されたschema.rb
ファイルをチェックすることはありません。
移行が手に負えなくなり、実行に時間がかかりすぎる場合、またはデータベースを適切に作成できなくなった場合、開発者は、古い移行ディレクトリをクリアし、新しいスキーマをダンプして、そこから続行することを恐れないでください。 新しい開発環境をセットアップするには、ほとんどの開発者が依存しているrake db:migrate
ではなく、 rake db:schema:load
が必要になります。
これらの問題のいくつかは、Railsガイドでも説明されています。
よくある間違い#10:機密情報をソースコードリポジトリにチェックインする
Railsフレームワークを使用すると、さまざまな種類の攻撃を受けない安全なアプリケーションを簡単に作成できます。 これのいくつかは、ブラウザとのセッションを保護するために秘密のトークンを使用することによって達成されます。 このトークンは現在config/secrets.yml
に保存されており、そのファイルは本番サーバーの環境変数からトークンを読み取りますが、Railsの過去のバージョンではconfig/initializers/secret_token.rb
にトークンが含まれていました。 このファイルは、アプリケーションの残りの部分と一緒にソースコードリポジトリに誤ってチェックインされることがよくあります。これが発生すると、リポジトリにアクセスできる人は誰でも、アプリケーションのすべてのユーザーを簡単に危険にさらす可能性があります。
したがって、リポジトリ構成ファイル(たとえば、gitユーザーの場合は.gitignore
)がトークンを含むファイルを除外していることを確認する必要があります。 その後、本番サーバーは、環境変数またはdotenvgemが提供するようなメカニズムからトークンを取得できます。
チュートリアルのまとめ
Railsは、堅牢なWebアプリケーションを構築するために必要な醜い詳細の多くを隠す強力なフレームワークです。 これによりRailsWebアプリケーションの開発がはるかに高速になりますが、開発者は潜在的な設計およびコーディングエラーに注意を払い、アプリケーションが成長するにつれて簡単に拡張および保守できるようにする必要があります。
開発者は、アプリケーションの速度が低下し、信頼性が低下し、安全性が低下する可能性のある問題にも注意する必要があります。 フレームワークを研究し、開発プロセス全体で行っているアーキテクチャ、設計、およびコーディングのトレードオフを完全に理解して、高品質で高性能なアプリケーションを確保することが重要です。