Railsでのオブゞェクト指向開発の秘密

本日は、今幎のRuby Hero Awardの受賞者の1人であり、Rubyの信奉者である有名な開発者である Steve KlabnikSteve Klabnikの投皿の翻蚳をご玹介したす。 この報酬は䜕ですか 有意矩な孊習コンテンツの䜜成、プラグむンず宝石の開発、オヌプン゜ヌスプロゞェクトぞの参加など、最も実瞟のあるコミュニティメンバヌに昚幎の受賞者から授䞎されたす。 この賞は、最も自明な人々を称賛し、圌らにふさわしいずいう認識を䞎えるために創蚭されたした。

今幎11月5〜6日にキ゚フで開催されるRubyCカンファレンスでSteveず話すこずができたす。



私はよくRailsでRubyを教えたず人々に蚀いたす。 これは最悪の方法の1぀ですが、その頃にはすでに倚くのプログラミング蚀語を孊んでいたので、気にしたせんでした。 ただし、これにより、Railsアプリケヌションに必芁なクラスがどれほど慎重に蚭蚈されおいるかに぀いお、少しゆがんだ感芚が埗られたした。 幞いなこずに、私は他人によっお曞かれたコヌドを偏芋でレビュヌしおおり、私が尊敬する倚くの人々の発展に重芁なこずが䞀぀あるこずに気づきたした。



これらの人々もこのこずをナニヌクだず考えおいるようです。 これは、良いコヌドの曞き方を知らない人が詊しおみるずきではありたせんが、それでもひどく刀明したす。 それは旗、信号のようなものです。 さお、私が誰かがこのこずをどのように実装するかを芋たずき、私はすぐに考えたす「圌はumbleる」。 私は自分の気持ちを信じすぎるかもしれたせんが、この高床な開発手法は、Railsアプリケヌションに盞互接続された倚くの利点を提䟛し、䜿いやすく、テストを1桁以䞊高速化したす。 残念ながら、倚くの初心者のRails開発者にずっおこれは明らかではありたせんが、コヌドをより良く曞いおいただきたいず思いたす。







「叀いシンプルなRubyオブゞェクト」ず呌ばれたす



はい、正確に。 Rubyは䜕も継承しないクラスです。 ずおもシンプルなので、目立぀堎所に隠されおいたす。 Railsの䜜成者に愛されおいる、叀いシンプルなRubyオブゞェクト、たたは「PORO」は、䞀郚の人々がそれらを呌ぶこずを奜むように、耇雑さに察する隠された歊噚です。 これが私の意味です。 この「単玔な」モデルを怜蚎しおください。

Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  1. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  2. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  3. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  4. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  5. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  6. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  7. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  8. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end



  9. Copy Source | Copy HTML class Post < ActiveRecord::Base def self .as_dictionary dictionary = ( 'A' .. 'Z' ).inject({}) {|h, l| h[l] = []; h} Post .all.each do | p | dictionary[ p .title[ 0 ]] << p end dictionary end end





すべおの投皿の最初の文字でむンデックスを衚瀺したす。 これを行うには、蟞曞を䜜成し、そこに投皿を配眮したす。 リストをペヌゞに分割する必芁がないので、デヌタベヌスからのすべおの投皿のリク゚ストに泚意を払わないずしたす。 アむデアは重芁です。すべおの投皿を名前で衚瀺できるようになりたした。

Copy Source | Copy HTML



  1. -Post.as_dictionary do |レタヌ、リスト|
  2.  p =文字
  3. ul
  4. -list.each do | post |
  5. li = link_to post


もちろん。 䞀方で、コヌドは悪くありたせん。 しかし、それも良くありたせん。ビゞネスロゞック甚に蚭蚈されたモデル内のプレれンテヌションが心配です。 プレれンタヌパタヌンを䜿甚しおこれを修正したしょう。



Copy Source | Copy HTML



  1. クラス DictionaryPresenter
  2. def initialize コレクション
  3. @collection =コレクション
  4. 終わり
  5. def as_dictionary
  6. 蟞曞= 'A' .. 'Z' .inject{}{| h、l | h [l] = []; h}
  7. @ collection.each do | p |
  8. 蟞曞[ p .title [ 0 ]] << p
  9. 終わり
  10. 蟞曞
  11. 終わり
  12. 終わり


これで、DictionaryPresenter.newPost.all.as_dictionaryを䜿甚できたす。 これには倚くの利点がありたす。モデルからプレれンテヌションロゞックを削陀したした。 すでに新しい機胜が远加されおいたす。どのコレクションもポむンタで衚瀺できるようになりたした。 この代衚的なクラスに察しお個別のテストを簡単に曞くこずができ、それらは高速になりたす。



代衚パタヌンに察する私の愛にもかかわらず、この投皿は圌に関するものではありたせん。 この原則は、「この抂念は独自のクラスに倀する」ずいう他の堎所にも珟れおいたす。 別の䟋に移る前に、この䟋を拡匵したしょう。投皿を名前で䞊べ替える堎合、このクラスは機胜したすが、ナヌザヌにはタむトルタむトルフィヌルドがないため、たずえばナヌザヌを衚瀺するこずはできたせん。 さらに、名前は蚘事「a」で始たるこずが倚いため、「A」に関する倚数の投皿を受け取りたす。実際、2番目の単語の最初の文字が必芁です。 2皮類の代衚者を䜜成できたすが、コミュニティが倱われ、「むンデックス」の抂念に再び2人の代衚者がシステムに远加されたす。 あなたが正しく理解したようにPOROは私たちを救いたす



代衚を少し倉曎しお、ポリシヌオブゞェクトを受け入れたす。



Copy Source | Copy HTML



  1. クラス DictionaryPresenter
  2. def 初期化 ポリシヌ、コレクション
  3. @policy =ポリシヌ
  4. @collection =コレクション
  5. 終わり
  6. def as_dictionary
  7. 蟞曞= 'A' .. 'Z' .inject{}{| h、l | h [l] = []; h}
  8. @ collection.each do | p |
  9. 蟞曞[@ policy.category_for p ] << p
  10. 終わり
  11. 蟞曞
  12. 終わり
  13. 終わり


これで、ポリシヌを远加しお、異なるポリシヌを䜜成できたす。

Copy Source | Copy HTML



  1. クラス UserCategorizationPolicy
  2. def self .category_forナヌザヌ
  3. user.username [ 0 ]
  4. 終わり
  5. 終わり
  6. クラス PostCategorizationPolicy
  7. def self .category_for投皿
  8. if post.starts_with "A" 
  9. post.title。 split [ 1 ] [ 0 ]
  10. 他に
  11. post.title [ 0 ]
  12. 終わり
  13. 終わり
  14. 終わり


バム

Copy Source | Copy HTML



  1. DictionaryPresenter。 新芏 PostCategorizationPolicy、Post.all.as_dictionary


はい、少し長くなっおいたす。 それは起こりたす:)しかし今、あなたは各抂念が私たちのシステムに䞀぀のアむデアを持っおいるこずがわかりたす。 代衚者は、物事がどのように配眮されるかを気にせず、政治家だけがそれらがどのように配眮されるかを指瀺したす。 実際、私の名前は少しがらくたです。おそらく、実際には「UsernamePolicy」たたは「TitlePolicy」ず呌ぶ方が良いでしょう。 私たちは圌らがどんなクラスなのかさえ気にしたせん



そしおすべおにおいお。 Rubyの柔軟性ず「継承コヌドを䜿甚した効果的な䜜業」の私のお気に入りの䟋を組み合わせるこずで、耇雑な蚈算をオブゞェクトに倉えるこずができたす。 このコヌドを芋おください



Copy Source | Copy HTML



  1. クラス Quote < ActiveRecord :: Base
  2. <snip>
  3. def pretty_turnaround
  4. タヌンアラりンドの堎合は 「」を 返したす。 れロ 
  5. purchase_atの堎合
  6. オフセット= purchase_at
  7. days_from_today = Time .now-purchase_at.to_time/ 60/60/24.floor + 1
  8. 他に
  9. オフセット= Time .now
  10. days_from_today =所芁時間+ 1
  11. 終わり
  12. 時間=オフセット+タヌンアラりンド* 60 * 60 * 24 
  13. if time.strftime "a" == "Sat" 
  14. 時間+ = 2 * 60 * 60 * 24
  15. elsif time.strftime "a" == "Sun" 
  16. 時間+ = 1 * 60 * 60 * 24
  17. 終わり
  18. 「{time.strftime " AdB "}{days_from_today}今日から営業日」
  19. 終わり
  20. 終わり


ああ このメ゜ッドは戻り蚈算時間を衚瀺したすが、ご芧のずおり、これは耇雑な蚈算です。 メ゜ッド抜出Method Extractリファクタリングメ゜ッドを数回䜿甚しお分割するず、これをより簡単に理解できたすが、時間の蚈算にのみ必芁な倚くのコヌドでQuoteクラスを詰たらせる危険がありたす。 たた、モデルがプレれンテヌションロゞックを実装しおいるずいう事実を芋ないでください。これは面倒なコヌドの䟋にすぎたせん。



したがっお、このリファクタリングの最初のステップは、Feather「レガシヌコヌドを効果的に䜿甚する」の著者が「メ゜ッドオブゞェクトのブレヌクアりト」ず呌ぶものです。 330ペヌゞの「レガシヌコヌドを効果的に䜿甚する」のコピヌを開き、詳现を読むこずができたす。 持っおいない堎合は、賌入しおください:)。 実際、気が散っおしたいたした。 アクションプランは次のずおりです。

1.新しい蚈算クラスを䜜成したす。

2.新しい方法で䜜業する方法を定矩したす。

3.叀いメ゜ッドの本䜓をコピヌし、ポむンタヌぞの参照をオブゞェクトに眮き換えたす。

4.ステップ3で䜿甚される倉数を割り圓おるための匕数を取るコンストラクタヌを䜜成したす。

5.叀いメ゜ッドを新しいクラスずメ゜ッドに委任したす。



コンパむラLean On The Compilerに頌るこずができないため、Rubyの元のテンプレヌトを少し倉曎し、Featherがこれに察凊するいく぀かの手順を実行したした。 ずにかく、このコヌドを詊しおみたしょう。 ステップ1



Copy Source | Copy HTML



  1. クラス Quote < ActiveRecord :: Base
  2. def pretty_turnaround
  3. スニップ
  4. 終わり
  5. クラス TurnaroundCalculator
  6. 終わり
  7. 終わり


第二



Copy Source | Copy HTML



  1. クラス TurnaroundCalculator
  2. def 蚈算
  3. 終わり
  4. 終わり


第䞉



Copy Source | Copy HTML



  1. クラス TurnaroundCalculator
  2. def 蚈算
  3. @turnaroundの堎合は 「」を返したす。 れロ 
  4. if @purchased_at
  5. オフセット= @purchased_at
  6. days_from_today = Time .now-purchase_at.to_time/ 60/60/24.floor + 1
  7. 他に
  8. オフセット= Time .now
  9. days_from_today = @turnaround + 1
  10. 終わり
  11. 時間=オフセット+@タヌンアラりンド* 60 * 60 * 24 
  12. if time.strftime "a" == "Sat" 
  13. 時間+ = 2 * 60 * 60 * 24
  14. elsif time.strftime "a" == "Sun" 
  15. 時間+ = 1 * 60 * 60 * 24
  16. 終わり
  17. 「{time.strftime " AdB "}{days_from_today}今日から営業日」
  18. 終わり
  19. 終わり


最初に通垞の名前を付けお、圌が正確に䜕をするのかが明確になった埌、ステップ5で名前を倉曎したいです。 ほずんどの堎合、コヌド自䜓が適切な名前を教えおくれたす。



4番目



Copy Source | Copy HTML



  1. クラス TurnaroundCalculator
  2. def 初期化 purchased_at、タヌンアラりンド
  3. @purchased_at = purchase_at
  4. @turnaround =タヌンアラりンド
  5. 終わり
  6. def 蚈算
  7. スニップ
  8. 終わり
  9. 終わり


5番目



Copy Source | Copy HTML



  1. クラス Quote < ActiveRecord :: Base
  2. def pretty_turnaround
  3. TurnaroundCalculator。 新芏 purchased_at、タヌンアラりンド.calculate
  4. 終わり
  5. 終わり


できた テストを実行し、どのように合栌するかを確認する必芁がありたす。 「テストを実行する」ずは、手動でチェックするこずを意味する堎合でも...



それでは、利点は䜕ですか さお、これでリファクタリングプロセスを開始できたすが、私たちは自分の小さなクリヌンルヌムにいたす。 Quoteクラスを詰たらせるこずなくTurnaroundCalcuatorクラスにメ゜ッドを远加できたす。Calculatorのみのクむックテストを蚘述し、埌で簡単に倉曎できるコンピュヌティングのアむデアを1か所で分解できたす。 いく぀かのリファクタリング埌のクラスは次のずおりです。



Copy Source | Copy HTML



  1. クラス TurnaroundCalculator
  2. def 蚈算
  3. @turnaroundの堎合は 「」を返したす。 れロ 
  4. 「{arrival_date}{days_from_today}本日からの営業日」
  5. 終わり
  6. 保護された
  7. def 到着日
  8. real_turnaround_time .strftime "AdB" 
  9. 終わり
  10. def real_turnaround_time
  11. adjust_time_for_weekends  start_time + turnaround_in_seconds 
  12. 終わり
  13. def adjust_time_for_weekends 時間
  14. if saturday 時間
  15. 時間+ 2 * 60 * 60 * 24
  16. elsif sunday 時間
  17. 時間+ 1 * 60 * 60 * 24
  18. 他に
  19. 時間
  20. 終わり
  21. 終わり
  22. def saturday 時間
  23. time.strftime "a" == "土"
  24. 終わり
  25. def sunday 時間
  26. time.strftime "a" == "Sun"
  27. 終わり
  28. def turnaround_in_seconds
  29. @タヌンアラりンド* 60 * 60 * 24
  30. 終わり
  31. def start_time
  32. @purchased_at たたは Time .now
  33. 終わり
  34. def days_from_today
  35. if @purchased_at
  36.  Time .now-@ purchase_at.to_time/ 60/60/24.floor + 1
  37. 他に
  38. @タヌンアラりンド+ 1
  39. 終わり
  40. 終わり
  41. 終わり


わあ 3幎前に曞いたこのコヌドは完党ではありたせんが、ほずんど理解できたす。 そしお、すべおの䜜品は理にかなっおいたす。 これは、リファクタリングの2぀たたは3぀の波の埌です。これは、別の投皿で明らかにするかもしれたせん。 いずれにせよ、あなたはその考えを理解しおいたす。 これは、Rubyの5行のメ゜ッドに焊点を合わせおいるずいうこずです。 コヌドが明確であれば、簡単に線集できたす。



Rubyのクリヌンオブゞェクトを取埗するずいう考えは、Railsでも圓おはたりたす。 このルヌトを芋おください



Copy Source | Copy HTML



  1. ルヌトto => 'dashboardindex' 、constraint => LoggedInConstraint


え LoggedInConstraint



Copy Source | Copy HTML



  1. クラス LoggedInConstraint
  2. def self .matchesリク゚スト
  3. current_user
  4. 終わり
  5. 終わり


A.はい。 ルヌティングポリシヌを蚘述するオブゞェクト。 いいね 怜蚌の䟋もomgbloglolで恥知らずに盗たれたした 



Copy Source | Copy HTML



  1. def SomeClass < ActiveRecord ::ベヌス
  2. validatecategory_id、proper_category => true
  3. 終わり
  4. クラス ProperCategoryValidator <ActiveModel :: EachValidator
  5. def validate_each レコヌド、属性、倀
  6. record.user.category_idsを陀きたす。 include 倀
  7. 蚘録。 ゚ラヌ .add属性、 「カテゎリが間違っおいたす。」
  8. 終わり
  9. 終わり
  10. 終わり


これは玔粋なRubyクラスではありたせんが、あなたはその考えを理解しおいたす。



「スティヌブ、Railsだけではない、嘘を぀いた」ず思うかもしれたせん。ええ、はい、実際、あなたは私を捕たえたした。 しかし、決しおクラスを壊さないずいうtrapにあなたを誘惑するように思われるRails特有の䜕かがありたす。 たぶんlib /はそのようなゎミリポゞトリのようです。 15分間の䟋にActiveRecordモデルのみが含たれおいる可胜性がありたす。 おそらく、非Railsを開くよりも閉じたRailsアプリケヌションの方が倚い泚私自身の意芋だけですので、信頌できる良い䟋はあたりありたせん。 Railsは䌁業向けのサむト開発によく䜿甚されるため、私はこの考えを持っおいたす。宝石たさに私のWebアプリケヌションそれほどではありたせん。それでも、確認する統蚈がありたせん。



䞀般的に、ドメむンオブゞェクトの取埗は適切です。 テストを高速化し、コヌドを短くし、将来の倉曎を容易にしたす。 私はただこれに぀いお、特に「クむックテスト」に぀いお䜕か蚀いたいこずがありたすが、私はすでに投皿の可胜な長さを䜿い果たしたした。 次回たで



All Articles