Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Object Oriented Design, Rails and Why You Should Think Twice Before Leaving the Rails Way

Object Oriented Design, Rails and Why You Should Think Twice Before Leaving the Rails Way

The Ruby community has been bombarded by a plethora of discussions about software design best practices. Acronyms such as SOLID, DCI and SOA are up in the community and all them promise to materialize the ghost of maintainability. What few of these discussions present is that applying these principles and techniques fervently also impacts the software maintenance.
In this talk I will present some of these concepts and show the symptoms that they can cause in yours projects if applied incorrectly.

Rafael França

March 06, 2015
Tweet

More Decks by Rafael França

Other Decks in Technology

Transcript

  1. MVC

  2. <!-- app/views/people/index.html.erb --> <% @people.each do |person| %> <div id="person-<%=

    person.new_record? ? "new" : person.id %>"> <span class="name"> <%= person.last_name %>, <%= person.first_name %> </span> <span class="age"> <%= (Date.today - person.birthdate) / 365 %> </span> </div> <% end %>
  3. # app/controllers/people_controller.rb class PeopleController < ActionController::Base def index @people =

    Person.where("added_at > ? and deleted = ?", Time.now.utc, false) .order("last_name, first_name") @people = @people.reject { |p| p.address.nil? } end end
  4. # app/models/person.rb class Person < ActiveRecord::Base has_one :address def name

    "#{last_name}, #{first_name}" end def age (Date.today - person.birthdate) / 365 end def pseudo_id new_record? ? "new" : id end end
  5. <!-- app/views/people/index.html.erb --> <% @people.each do |person| %> <div id="person-<%=

    person.pseudo_id %>"> <span class="name"><%= person.name %></span> <span class="age"><%= person.age %></span> </div> <% end %>
  6. # app/models/person.rb class Person < ActiveRecord::Base has_one :address def self.find_recent

    people = where("added_at > ? and deleted = ?", Time.now.utc, false). order("last_name, first_name") people.reject { |p| p.address.nil? } end def name "#{last_name}, #{first_name}" end def age (Date.today - person.birthdate) / 365 end def pseudo_id new_record? ? "new" : id end end
  7. # app/models/person.rb class Person < ActiveRecord::Base has_one :address def self.find_recent

    people = where("added_at > ? and deleted = ?", Time.now.utc, false). order("last_name, first_name") people.reject { |p| p.address.nil? } end def name "#{last_name}, #{first_name}" end def age (Date.today - person.birthdate) / 365 end def pseudo_id new_record? ? "new" : id end end
  8. # app/controllers/people_controller.rb class PeopleController < ActionController::Base before_action :build_person def create

    if @person.save head :created else render :new end end private def build_person @person.new(params[:person]) end end
  9. # app/models/comment.rb class Comment < ActiveRecord::Base belongs_to :commentable, polymorphic: true

    after_create -> { Notification.create! comment: self, recipients: commendable.recipients } end
  10. # app/models/comment.rb class Comment < ActiveRecord::Base belongs_to :commentable, polymorphic: true

    after_create -> { Notification.create! comment: self, recipients: commendable.recipients } end
  11. # app/models/concerns/copyable.rb module Copyable def copy_to(destination) Notification.suppress do # Copy

    logic that creates new comments that we do not want # triggering Notifications end end end
  12. break them apart into a higher level object (CreateCommentWithNotification, for

    instance) so that I had the freedom to only create the comment if I wanted to.
  13. break them apart into a higher level object (CreateCommentWithNotification, for

    instance) so that I had the freedom to only create the comment if I wanted to.
  14. break them apart into a higher level object (CreateCommentWithNotification, for

    instance) so that I had the freedom to only create the comment if I wanted to.
  15. break them apart into a higher level object (CreateCommentWithNotification, for

    instance) so that I had the freedom to only create the comment if I wanted to.
  16. The general principle I apply for this design is "the

    exception must carry the weight of its own work". If you create a new CreateCommentWithNotifications object, you're pushing the weight of the work onto the default case. That's not proportional in my book.
  17. The general principle I apply for this design is "the

    exception must carry the weight of its own work". If you create a new CreateCommentWithNotifications object, you're pushing the weight of the work onto the default case. That's not proportional in my book.
  18. The general principle I apply for this design is "the

    exception must carry the weight of its own work". If you create a new CreateCommentWithNotifications object, you're pushing the weight of the work onto the default case. That's not proportional in my book.
  19. <!-- app/views/people/index.html.erb --> <div id="person-<%= pseudo_id_for(person) %>"> <!-- app/views/comments/index.html.erb -->

    <div id="comments-<%= pseudo_id_for(comment) %>"> # app/helpers/person_helper.rb module PersonHelper def pseudo_id_for(person) person.new_record? ? "new" : person.id end end # app/helpers/person_helper.rb module CommentHelper def pseudo_id_for(comment) comment.new_record? ? "new_comment" : comment.uuid end end
  20. • Models too fat • Callbacks hard to avoid and

    test • Lots of conditionals in the code • Domain logic coupled with the framework
  21. SOLID • Single responsibility principle • Open/closed principle • Liskov

    substitution principle • Interface segregation principle • Dependency inversion principle
  22. DCI

  23. • Componentization via Services • Organized around Business Capabilities •

    Smart endpoints and dumb pipes • Decentralized Governance • Decentralized Data Management • Design for failure • Evolutionary Design
  24. In a nutshell: Trailblazer makes you write logicless models that

    purely act as data objects, don't contain callbacks, nested attributes, validations or domain logic. It removes bulky controllers and strong_parameters by supplying additional layers to hold that code and completely replaces helpers.
  25. It solves many problems that have been around for years

    with a cleanly layered architecture. Only use what you like.
  26. While Trailblazer offers you abstraction layers for all aspects of

    Ruby On Rails, it does not missionize you. Wherever you want, you may fall back to the "Rails Way" with fat models, monolithic controllers, global helpers, etc. This is not a bad thing