A little bit about me
@moove_it
/moove-it
@aragno157
/lucas-aragno
Train derailment and subsequent unstoppable fireball
laragno$ cat app/controllers/application_controller.rb | wc -l
569
laragno$
laragno$ cat app/models/user.rb | wc -l
571
laragno$
So we need to fix this
@conradirwin
You have models, which are nice self-contained bits of state
You have Views which are nice self-contained bits of UI
You have Controllers which are nice self-contained bits of …
They are responsible for making changes to your models, for showing the right views at the right time, and for responding to events triggered by user interactions. In a well factored application, each sub-operation can be run independently of its parent.
Listening on events is what gives MOVE (and MVC) the inversion of control that you need to allow models to update views without the models being directly aware of which views they are updating. This is a powerful abstraction technique, allowing components to be coupled together without interfering with each other.
└── app
├── concepts
└── speaker
├── cell
│ ├── index.rb
│ └── show.rb
├── contract
│ ├── create.rb
│ └── update.rb
├── operation
│ ├── create.rb
│ └── update.rb
└── view
├── index.haml
└── show.haml
class SpeakersController < ApplicationController
def create
Speakers::Create.(params)
end
end
class SpeakersController < ApplicationController
def create
run Speaker::Create do |op|
return redirect_to(speaker_path op.model)
end
render :new
end
end
It's a simple orchestrator between the form object, models and your business code.
class Speaker < ActiveRecord::Base
class Create < Trailblazer::Operation
def process(params)
Speaker.create(params)
end
end
end
class Speaker < ActiveRecord::Base
class Create < Trailblazer::Operation
contract do
property :name, validates: {presence: true}
end
def process(params)
@model = Speaker.new
validate(params, @model) do |f|
f.save!
end
end
end
end
class Speaker < ActiveRecord::Base
class Create < Trailblazer::Operation
contract Contract::Create
def process(params)
@model = Speaker.new
validate(params, @model) do |f|
f.save!
end
end
end
end
module Contract
class Create
properties :name
validates :name, presence: true
end
end
class Speaker::Policy
def initialize(user, speaker)
@user, @speaker = user, speaker
end
def create?
@user.admin?
end
end
class Speaker < ActiveRecord::Base
class Create < Trailblazer::Operation
include Policy
policy Speaker::Policy, :create?
end
end
class Speaker < ActiveRecord::Base
class Create < Trailblazer::Operation
include Callback
# ...
callback(:after_save) do
on_change :markdownize_description!
end
# ...
end
end
class Speaker < ActiveRecord::Base
class Create < Trailblazer::Operation
include Callback
# ...
def process(params)
validate(params) do
contract.save
callback!(:after_save)
end
end
def markdownize_description!(speaker)
speaker.description = Markdownize.(speaker.description)
end
end
end
class Speaker < ActiveRecord::Base
belongs_to :conference
scope :first_day, lambda { where(speaks_on_the_first_day: true) }
end
describe Speaker::Update do
let(:speaker) { Speaker::Create.(...) }
# do you thing
end
`The way helpers are being used in Rails encourages users to write “script-like” code using global variables and procedural functions in an environment that calls itself “object-oriented”.` @apotonick
class SpeakerCell < Cell::ViewModel
property :name
property :description
property :avatar_url
def show
render
end
private
def avatar
image_tag avatar_url
end
end
<!-- wow so logic less -->
<div class="speaker-box">
<%= avatar %>
<h3><%= name %></h3>
<p>
<%= description %>
</p>
</div>
<!-- ... -->
<% @speakers.each do |speaker| >
<%= cell(:speaker, speaker) %>
<% end %>
Switching to microservices it's not a thing that you can decide from one day to another, maybe a good object design it's all what you need
(Not as much as Jerry does)
Nick Sutterer
Bryan Helmkamp