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

Trailblazer - A New Architecture For Rails

Rails Pacific
September 27, 2014

Trailblazer - A New Architecture For Rails

Speaker: Nick Sutterer

The Rails Way has accomplished its mission and jump-started thousand and ten-thousands of projects from an initial idea to a running web application, sometimes within weeks. Lots of those projects are still running, however, they have turned into code hells you better not touch or you might break something. This is a result of the lack of abstraction layers in this great framework.

Trailblazer offers you 7 additional places to put your code in a Rails app, comes with a more intuitive file structure and allows you to develop components for a rock-solid system architecture. It's layered design is completely optional and non-intrusive - you pick where you need more abstractions.

Let's explore Trailblazer's concept orientation, new abstractions as view models, form objects, operations and domain layers, representers and more and discuss this new architectural style over a cup of coffee.

Rails Pacific

September 27, 2014
Tweet

More Decks by Rails Pacific

Other Decks in Technology

Transcript

  1. enterprise bild raumschiff, 3 abstraktionsschichten sind nicht genug für “enterprise”

    applikationen (verdammt, das ist nunmal, was wir machen!)
  2. app ├── concepts │ ├── comment │ │ ├── cell.rb

    │ │ ├── views │ │ │ ├── show.haml │ │ │ ├── list.haml │ │ ├── assets │ │ │ ├── comment.css.sass │ │ ├── form.rb │ │ ├── operation.rb │ │ ├── operation │ │ │ ├── create.rb │ │ ├── twin.rb
  3. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  4. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  5. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  6. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  7. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  8. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  9. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  10. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  11. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  12. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  13. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  14. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  15. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  16. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) @users = Follower.find_for(model) validate do save! send_notification! end end end
  17. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end end end
  18. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end
  19. module Comment class Create < Trailblazer::Operation class Contract < Reform::Form

    property :body validates :body, presence: true end include Worker def process(params) # model = Comment.find(params[:id]) validate do save! send_notification! end end
  20. v

  21. # app/models/comment.rb class Comment < ActiveRecord::Base attr_accessible :author_attributes, :body accepts_nested_attributes_for

    :author validates :author, :body, :presence => true, :unless => :notification? before_validation :no_create_when_closed, :on => :create, :if => :forum? end
  22. # app/models/comment.rb class Comment < ActiveRecord::Base attr_accessible :author_attributes, :body accepts_nested_attributes_for

    :author validates :author, :body, :presence => true, :unless => :notification? before_validation :no_create_when_closed, :on => :create, :if => :forum? end
  23. # app/models/comment.rb class Comment < ActiveRecord::Base attr_accessible :author_attributes, :body accepts_nested_attributes_for

    :author validates :author, :body, :presence => true, :unless => :notification? before_validation :no_create_when_closed, :on => :create, :if => :forum? end
  24. # app/models/comment.rb class Comment < ActiveRecord::Base attr_accessible :author_attributes, :body accepts_nested_attributes_for

    :author validates :author, :body, :presence => true, :unless => :notification? before_validation :no_create_when_closed, :on => :create, :if => :forum? end
  25. # app/models/comment.rb class Comment < ActiveRecord::Base attr_accessible :author_attributes, :body accepts_nested_attributes_for

    :author validates :author, :body, :presence => true, :unless => :notification? before_validation :no_create_when_closed, :on => :create, :if => :forum? end
  26. # app/models/comment.rb class Comment < ActiveRecord::Base attr_accessible :author_attributes, :body accepts_nested_attributes_for

    :author validates :author, :body, :presence => true, :unless => :notification? before_validation :no_create_when_closed, :on => :create, :if => :forum? end
  27. # app/concepts/comment/form.rb class Comment::Form < Reform::Form property :body property :author

    do property :email validates :email, presence: true end validates :author, :body, presence: true end
  28. # app/concepts/comment/form.rb class Comment::Form < Reform::Form property :body property :author

    do property :email validates :email, presence: true end validates :author, :body, presence: true end
  29. # app/concepts/comment/form.rb class Comment::Form < Reform::Form property :body property :author

    do property :email validates :email, presence: true end validates :author, :body, presence: true end
  30. # app/concepts/comment/form.rb class Comment::Form < Reform::Form property :body property :author

    do property :email validates :email, presence: true end validates :author, :body, presence: true end
  31. form.validate( body: "Cool!", eviiiiiiiiiiil: 0x13370x1337 ) class Comment::Form < Reform::Form

    property :body property :author do property :email validates :email, presence: true end validates :author, :body, presence: true end
  32. # spec/comment/form_spec.rb let(:comment) { Comment.new } let(:form) { Comment::Form.new(comment) }

    it do form.validate( body: "Cool!", author: {email: "[email protected]"} ).must_equal true form.save comment.email.must_equal "[email protected]" end
  33. # spec/comment/form_spec.rb let(:comment) { Comment.new } let(:form) { Comment::Form.new(comment) }

    it do form.validate( body: "Cool!", author: {email: "[email protected]"} ).must_equal true form.save comment.email.must_equal "[email protected]" end *
  34. # spec/comment/form_spec.rb let(:comment) { Comment.new } let(:form) { Comment::Form.new(comment) }

    it do form.validate( body: "Cool!", author: {email: "[email protected]"} ).must_equal true form.save comment.email.must_equal "[email protected]" end
  35. # spec/comment/form_spec.rb # invalid it do form.validate( body: "Cool!", ).must_equal

    false form.errors.must_equal {author: ["can't be blank"]} end *
  36. # spec/comment/form_spec.rb # invalid it do form.validate( body: "Cool!", ).must_equal

    false form.errors.must_equal {author: ["can't be blank"]} end
  37. Model Model Form class Comment::TopicForm < Reform::Form property :body, on:

    :comment property :title, on: :topic validates :title, :body, presence: true end
  38. Model Model Form class Comment::TopicForm < Reform::Form property :body, on:

    :comment property :title, on: :topic validates :title, :body, presence: true end
  39. # app/concepts/comment/form.rb class Comment::Form < Reform::Form property :body property :author

    do property :email validates :email, presence: true end validates :author, :body, presence: true end
  40. # app/concepts/comment/form.rb class Comment::Form < Reform::Form property :body property :author

    do property :email validates :email, presence: true end validates :author, :body, presence: true end
  41. # app/helpers/comments_helper.rb def render_comment(comment) is_owner = (comment.author == current_user) render

    partial: “shared/comment”, locals: {comment: comment, is_owner: is_owner} end
  42. # app/helpers/comments_helper.rb def render_comment(comment, env=:shared) is_owner = (comment.author == current_user)

    render partial: “shared/comment”, locals: {comment: comment, is_owner: is_owner} end
  43. # app/helpers/comments_helper.rb def render_comment(comment, env=:shared) is_owner = (comment.author == current_user)

    render partial: "#{env}/comment", locals: {comment: comment, is_owner: is_owner} end
  44. # app/helpers/comments_helper.rb def render_comment(comment, env=:shared) is_owner = (comment.author == current_user)

    render partial: "#{env}/comment", locals: {comment: comment, is_owner: is_owner} end
  45. # app/views/shared/_comment.html.haml .comment .text = comment.body .author - type =

    comment.staff? ? :thumb : :staff = image_tag comment.image.url
  46. # app/views/shared/_comment.html.haml .comment .text = comment.body .author - type =

    comment.staff? ? :thumb : :staff = image_tag comment.image.url
  47. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    end # app/concepts/comment/views/show.haml %h1 Hello, Taiwan!
  48. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    end # app/concepts/comment/views/show.haml %h1 Hello, Taiwan!
  49. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    end # app/concepts/comment/views/show.haml .comment .text = body .author = avatar
  50. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    end # app/concepts/comment/views/show.haml .comment .text = body .author = avatar
  51. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    end # app/concepts/co .comment .text = body .author = avatar
  52. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    private def body end end # app/concepts/co .comment .text = body .author = avatar
  53. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    private def body model.body end end # app/concepts/co .comment .text = body .author = avatar
  54. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    private def body model.body end def avatar image_tag model.image.url end end # app/concepts/co .comment .text = body .author = avatar
  55. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    private def body model.body end def avatar image_tag model.image.url end end # app/concepts/co .comment .text = body .author = avatar
  56. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    private property :body def avatar image_tag model.image.url end end # app/concepts/co .comment .text = body .author = avatar
  57. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel def show render end

    private property :body property :image def avatar image_tag model.image.url end end # app/concepts/co .comment .text = body .author = avatar
  58. # app/concepts/comment/cell.rb class Comment::Cell < Cell::ViewModel property :body property :image

    def show render end private def avatar image_tag image.url end end # app/concepts/co .comment .text = body .author = avatar
  59. # app/concepts/comment/cel class Comment::Cell < Cell property :body property :image

    def show render end private def avatar image_tag image.url end end
  60. Features: No AV (probably in Rails 5.0) Layouts Caching View

    inheritance 3x faster (even though we create “thousands of objects”) collection rendering polymorphic cells Wrong Font TM