DCI with Ruby

2e7087b86608d4497c209eb9ba14d8f5?s=47 Sho Kusano
December 25, 2012

DCI with Ruby

DCI の使い方を考える

2e7087b86608d4497c209eb9ba14d8f5?s=128

Sho Kusano

December 25, 2012
Tweet

Transcript

  1. DCI with Ruby わかった気になる DCI

  2. こんにちは! ロージーです!

  3. Dec 21 Dec 22 Dec 23 今日はこの4日間で僕が学んだ DCI の話をします (クリスマスなんて知ったこっちゃねぇ)

    Dec 24
  4. 1. What is DCI

  5. Data Context Interaction データ 文脈 挙動

  6. None
  7. 全然わかんない! 教えて!マスター=センセイ!

  8. Ruby on Rails: The Bad Parts https://vimeo.com/50568777

  9. DCI and the application builds our mental models https://vimeo.com/51957143

  10. The Right Way to Code DCI in Ruby http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby

  11. and Github Repositories Alias DCI https://github.com/rebo/alias_dci DCI-Sample https://github.com/vsavkin/DCI-Sample DCI https://github.com/rubyworks/dci

  12. Data ex: User Behavior ex: Customer Story ex: Buy the

    Item Data Context Interaction
  13. 2. Where is Logics

  14. 『ユーザーが商品を買う』 というロジックはどこにあるべきか論

  15. 例をご用意しました 『ユーザーがアイテムを買う』 User has_many :purchases Purchase belongs_to :user belongs_to :item

    Item has_many :purchases
  16. Case 1: Model class User def buy_item(item) self.purchases.create(item: item) end

    end https://gist.github.com/285ec33e6cfceb0ba0cf#file-case_1_model-rb
  17. Case 2: Controller class PurchasesController def create item = Item.find(params[:item_id])

    current_user.purchases.create(item: item) end end https://gist.github.com/285ec33e6cfceb0ba0cf#file-case_2_controller-rb
  18. Case 3: DCI class BuyItemContext def initialize(user, item) @user =

    user @item = item @user.extend(Customer) end def process @user.buy(item) end end module Customer def buy(item) self.purchases.create(item: item) end end https://gist.github.com/285ec33e6cfceb0ba0cf#file-case_3_dci-rb
  19. このサイズだと判断が難しいと思うけれど とりあえずコントローラにあるのはヤバそうな気配がする

  20. User#buy_item は、正しいのか

  21. Wikipedia 曰く Model View Controllerページ Model ͦͷΞϓϦέʔγϣϯ͕ѻ͏ྖҬͷσʔλͱखଓ͖ʢϏδωεϩδοΫ - γϣοϐϯάͷ߹ܭֹ΍ૹྉΛܭࢉ͢ΔͳͲʣΛදݱ͢ΔཁૉͰ͋Δɻ· ͨɺσʔλͷมߋΛviewʹ௨஌͢Δͷ΋modelͷ੹೚Ͱ͋Δʢmodelͷมߋ

    Λ௨஌͢ΔͷʹObserver ύλʔϯ͕༻͍ΒΕΔ͜ͱ΋͋Δʣɻ ଟ͘ͷΞϓϦέʔγϣϯͰ͸σʔλͷ֨ೲʹӬଓతͳهԱͷ࢓૊Έʢσʔ λϕʔεͳͲʣ͕࢖ΘΕ͍ͯΔɻMVCͷ֓೦Ͱ͸ɺσʔλͷʢUIҎ֎ͷʣ ೖग़ྗ͸औΓѻΘͳ͍ͷͰɺσʔλΞΫηε΋ຊདྷMVCͷ֓೦ͷൣᙝΛ௒ ͑Δ΋ͷͰ͸͋Δ͕ɺ׶͍͑ͯ͑͹modelͷதʹӅṭ͞ΕΔͱߟ͑ΒΕΔɻ
  22. 正しいみたい!

  23. None
  24. お疲れ様でした! まだ続きます

  25. 3. be Fat

  26. # Redmine - project management software # Copyright (C) 2006-2012

    Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require "digest/sha1" class User < Principal include Redmine::SafeAttributes # Different ways of displaying/sorting users USER_FORMATS = { :firstname_lastname => { :string => '#{firstname} #{lastname}', :order => %w(firstname lastname id), :setting_order => 1 }, :firstname_lastinitial => { :string => '#{firstname} #{lastname.to_s.chars.first}.', :order => %w(firstname lastname id), :setting_order => 2 }, :firstname => { :string => '#{firstname}', :order => %w(firstname id), :setting_order => 3 }, :lastname_firstname => { :string => '#{lastname} #{firstname}', :order => %w(lastname firstname id), :setting_order => 4 }, :lastname_coma_firstname => { :string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id), Redmine app/models/user.rb
  27. Token.delete_all ['user_id = ?', id] Watcher.delete_all ['user_id = ?', id]

    WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] end # Return password digest def self.hash_password(clear_password) Digest::SHA1.hexdigest(clear_password || "") end # Returns a 128bits random salt as a hex string (32 chars long) def self.generate_salt Redmine::Utils.random_hex(16) end end class AnonymousUser < User validate :validate_anonymous_uniqueness, :on => :create def validate_anonymous_uniqueness # There should be only one AnonymousUser in the database errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists? end def available_custom_fields [] end # Overrides a few properties def logged?; false end def admin; false end def name(*args); I18n.t(:label_user_anonymous) end def mail; nil end def time_zone; nil end def rss_key; nil end def pref UserPreference.new(:user => self) end # Anonymous user can not be destroyed def destroy false end end Redmine app/models/user.rb 706 lines
  28. Token.delete_all ['user_id = ?', id] Watcher.delete_all ['user_id = ?', id]

    WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] end # Return password digest def self.hash_password(clear_password) Digest::SHA1.hexdigest(clear_password || "") end # Returns a 128bits random salt as a hex string (32 chars long) def self.generate_salt Redmine::Utils.random_hex(16) end end class AnonymousUser < User validate :validate_anonymous_uniqueness, :on => :create def validate_anonymous_uniqueness # There should be only one AnonymousUser in the database errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists? end def available_custom_fields [] end # Overrides a few properties def logged?; false end def admin; false end def name(*args); I18n.t(:label_user_anonymous) end def mail; nil end def time_zone; nil end def rss_key; nil end def pref UserPreference.new(:user => self) end # Anonymous user can not be destroyed def destroy false end end Redmine app/models/user.rb 706 lines
  29. コードがすごく汚いとかではない (むしろいい方だと思う)

  30. じゃあなんで太るのか

  31. たいてい参照されるケースが多すぎる (User がやたら太るのはその理由)

  32. 90%のユーザーストーリーで必要のない 振る舞いを抱え続けている

  33. 常に必要のない振る舞いは 適宜与えてあげればいい

  34. それって、DCIでは?

  35. 4. Rails with DCI

  36. 現状の Rails で DCI をするには 1. dci gem を頑張って使う(微妙) 2.

    DCI-Sample みたいに自分で頑張る(辛い) 3. user.extend(Customer) しまくる(妥当) 4. ESM に gem 化をお願いする(どうなん)
  37. AOP じゃダメなの?

  38. 僕の理解が間違ってなければ AOP って #alias_method_chain なのでは

  39. もう自作したほうが早いのでは?

  40. Dicer https://github.com/rosylilly/dicer 作ってみた

  41. WIP まだ実用に耐えない 僕にお酒を飲ませると実装が進みます!

  42. 5. the Future

  43. Rails 4 ちょっと見たけど、 こういう問題への解決策の提示はなかった

  44. このままだと、主要モデルが太り続けるのは 間違いない

  45. こんだけ喋ったけど 僕もまだいい方法が思いつかない

  46. モデルダイエット勉強会誰かやりませんか>< (僕 DCI の勉強進めて DCI の話します)

  47. 応質 答疑 Question https://speakerdeck.com/rosylilly/dci-with-ruby