モデルを乾燥する方法

ほとんどの鉄道プロジェクトでは、コードの主な集中はモデルに当てはまります。 おそらく誰もがスリムコントローラーとファットモデルについて読み、可能な限りモデルに押し込み、コントローラーにはできるだけ押し込まないようにしてください。 まあ、これは称賛に値しますが、モデルを厚くしようとする努力の中で、多くの場合、DRYの原則を忘れます-繰り返し(クソ)しないでください。

モデルエリアで魚を食べる方法を簡単に説明し、DRYを忘れないようにします。



したがって、私たちが持っているモデルはカットクラスです。 他のカットクラスと同様に、モデルは次の3つの要素で構成されます。

1)インスタンスメソッドの定義

class User < ActiveRecord::Base def name [ first_name, last_name ].filter(&:presence).compact.map(&:strip) * ' ' end end 


2)クラスメソッドの定義

 class User < ActiveRecord::Base def self.find_old where('created_at < ?', 1.year.ago).all end end 


3)「クラス」コード

 class User < ActiveRecord::Base attr_accessor :foo has_many :authentications, :dependent => :destroy has_many :invitees, :class_name => 'User', :as => :invited_by delegate :city, :country, :to => :location attr_accessible :admin, :banned, :as => :admin mount_uploader :userpic, UserPicUploader scope :admin, where(:admin => true) validates :username, :uniqueness => true, :format => /^[az][az\d_-]+$/, :length => { :within => 3..20 }, :exclusion => { :in => USERNAME_EXCLUSION } end 


実際にルビーの他のクラスのように、モデルを乾燥させる最も簡単な方法は、別々のモジュールで繰り返し部分を取り出すことです。 実際、モジュールはこれに役立つだけではありません。 悪くないこともあります。コードの巨大なゴミを別のファイルに取り出して、モデルがきれいに見え、大量の無関係なゴミが混同されないようにするだけです。

方法


そのため、インスタンスメソッドの定義により、すべてが可能な限りシンプルで明確になります。
 # user_stuff.rb module UserStuff def name [ first_name, last_name ].filter(&:presence).compact.map(&:strip) * ' ' end end # user.rb class User < ActiveRecord::Base include UserStuff end 


クラスメソッドは少し興味深いです。 これは機能しません:
 # user_stuff.rb module UserStuff def self.find_old where('created_at < ?', 1.year.ago).all end end # user.rb class User < ActiveRecord::Base include UserStuff end 

ここでは、 find_oldモジュールUserStufffind_oldメソッドを定義しますが、これはモデルには含まれません。
したがって、次のようにする必要があります。
 # user_stuff.rb module UserStuff def find_old where('created_at < ?', 1.year.ago).all end end # user.rb class User < ActiveRecord::Base extend UserStuff # ,  include,  extend end 


たとえば、次のように、インタンとクラスメソッドをモジュールに入れることができます。
 # user_stuff.rb module UserStuff module InstanceMethods def name [ first_name, last_name ].filter(&:presence).compact.map(&:strip) * ' ' end end module ClassMethods def find_old where('created_at < ?', 1.year.ago).all end end end # user.rb class User < ActiveRecord::Base include UserStuff::InstanceMethods extend UserStuff::ClassMethods end 


クラスコード


未解決の問題は、「クラス」コードをどうするかです。 通常、彼は最も多く、ほとんどの場合乾燥が必要です。

問題は、宣言されたクラス(この場合はモデル)のコンテキストで実行する必要があることです。 単純なモジュールは検証、多数のプラグイン、 has_manyなどについて何も知らないため、モジュール内に記述することは単に不可能です-すぐに実行しようとし、おそらく壊れるでしょう。 したがって、このモジュールがモデルに接続されている場合にのみ実行されるように、モジュールにコードを配置する必要があります。 幸い、ルビーではこれは非常に簡単です。

Moduleオブジェクトには、モジュールをどこかにインクルードするたびに呼び出されるメソッドが含まれています。 したがって、この問題の下で実行する必要があるコードを強制できます。 このように:
 module UserValidations def self.included(base) # base    — ,   . base.instance_eval do #     base #      validates :username, :uniqueness => true, :format => /^[az][az\d_-]+$/, :length => { :within => 3..20 }, :exclusion => { :in => USERNAME_EXCLUSION } validates :gender, :allow_blank => true, :inclusion => { :in => %w(mf) } end end end 


これで、モジュールの定義時にすべての検証コードが実行されるのではなく、静かに横たわり、このモジュールが非アクティブになるまで待機します。

すべての束


さて、これを上記のメソッド定義とどのように組み合わせるのでしょうか? そして、ここに方法があります:

 # user_stuff.rb module UserStuff def self.included(base) #     base.extend ClassMethods #    # Module#include —  .          #      base.include(InstanceMethods),    : base.send :include, InstanceMethods #       base.instance_eval do validates :gender, :presence => true end end #   module ClassMethods def find_old where('created_at < ?', 1.year.ago).all end end # - module InstanceMethods def name [ first_name, last_name ].filter(&:presence).compact.map(&:strip) * ' ' end end end # user.rb class User < ActiveRecord::Base include UserStuff end 


合計すると、モデルの任意の複雑な部分を安全に取り出すことができるモジュールがあり、次にこの部分を何回使用する必要があるかがわかります。 すべてがかなりクールです。 コードがいのはクールではなく、これらの無限のinclude / send:include / extendで簡単に混乱する可能性があります。

あなたは美しくなれます!


rubyコミュニティでは、コードの読みやすさと美しさ、および複雑さを隠す原理-いくつかのシンプルで美しいAPI / DSLの背後にある複雑なものを隠すことを高く評価しています。 RoRコードを見ると、ほぼすべての場所で上記のアプローチが使用されていることがすぐにわかります。 したがって、もちろん、彼らは人生を楽にすることに決め、 ActiveSupport::ConcernActiveSupport::Concern

このActiveSupport::Concernを使用するActiveSupport::Concernようにコードを書き換えることができます。
 module UserStuff extend ActiveSupport::Concern included do validates :gender, :presence => true end #   module ClassMethods def find_old where('created_at < ?', 1.year.ago).all end end # - module InstanceMethods def name [ first_name, last_name ].filter(&:presence).compact.map(&:strip) * ' ' end end end 


実際、このヘルパーの機能は、より美しいコードだけではありません。 彼にはまだいくつかの便利な機能がありますが、いつか個別にそれらについて書くかもしれません。 誰も待つことができません。lib lib/active_support/concern.rbの生のactive_supportを読んでもらいましょう。非常にクールでlib/active_support/concern.rbコメントがあり、コード例などがあります。

どこに置きますか?


プログラマーがすぐに遭遇する別の重要なポイント、誰が初めて自分のモデルを別々のモジュールに引き裂くことに決めたのか-コードを配置する場所とモジュールに名前を付ける方法?

明確な意見はおそらくないでしょうが、私自身は次のスキームを思いつきました。
 # lib/models/validations.rb module Models module Validations #   c     end end # lib/models/user/validation.rb module Models module User module Validations #    User end end end # app/models/user.rb class User < ActiveRecord::Base include Models::Validations include Models::User::Validations end 


ファイル名は重要です。すべてのコードを一度にロードせず、レールの自動ロードを使用できるためですModels::User::Validationsなどの未定義の定数に遭遇すると、最初にmodels/user/validations.rbファイルを検索してダウンロードしようとします、その後のみ、失敗した場合にパニックしてNameError例外をスローします。

おわりに


誰かが自分自身のためにこの記事から何か有用なものを引き出してくれることを願っています。そして、世界では、それがたった50分で少し読みにくいコードとモデルより少し少なくなります。

更新:
それから、 privaloffは私がコードに愚かさを持っていることに気付きました。 コードが修正されました。 同志プラスカルマのマインドフルネス。

Source: https://habr.com/ru/post/J125822/


All Articles