say goodbye to procedural programming - nick sutterer
Post on 11-Apr-2017
32 Views
Preview:
TRANSCRIPT
SAY GOODBYE TO
PROCEDURAL*PROGRAMMING
ANOTHER USELESS PRESENTATION BROUGHT TO YOU BY @APOTONICK
SAY GOODBYE TO
PROCEDURAL*PROGRAMMING
* AS WE KNOW IT.
<wrong>
REVEAL.JSAND HOW TO MASTER IT, PART I OF VIII
[ ] DIAGRAMS[ ] 6 MEMES[ ] 2 BULLET POINT LISTS[ ] QUOTE FROM SOMEONE[ ] MORE DIAGRAMS[ ] TRUCKLOADS OF CODE (you wanted it)
[ ] DIAGRAMS[ ] 6 MEMES[x ] 2 BULLET POINT LISTS[ ] QUOTE FROM SOMEONE[ ] MORE DIAGRAMS[ ] TRUCKLOADS OF CODE (you wanted it)
class Post < ActiveRecord::Base validates :body, presence:true
validates :author, presence:true
after_save :notify_moderators!, if: :create?
end
class PostsController < ApplicationController def create
return unless can?(current_user, Post, :new)
post = Post.new(author: current_user)
if post.update_attributes(
params.require(:post).permit(:title)
)
post.save
notify_current_user!
else
render :new
end
end
end
Let's not talk about persistence!
Let's not talk about business logic!
Let's not talk about views!
I SAID: RAILS VIEWS!
Notes:
let's come back to the problems in our example it'shard to understand what we are trying to do and:
HOW DO I TEST THAT?
class Post < ActiveRecord::Base validates :body, presence:true
validates :author, presence:true
after_save :notify_moderators!, if: :create?
end
describe Post do
it "validates and notifies moderators" do
post = Post.create( valid_params )
expect(post).to be_persisted
end
end
class Post < ActiveRecord::Base
validates :body, presence:true
validates :author, presence:true
after_save :notify_moderators!, if: :create?
end
describe Post do
it "validates and notifies moderators" do
post = Post.create( valid_params )
expect(post).to be_persisted
end
end
it do
controller = Controller.new
controller.create( valid_params )
expect(Post.last).to be_persisted
end
describe BlogPostsController do
it "creates BlogPost model" do
post :create, blog_post: valid_params
expect(response).to be_ok
expect(BlogPost.last).to be_persisted
end
end
...THINKING...
...THINKING...
[...] It extends the basic MVC patternwith new abstractions.
NO!
class MyService def self.call(args)
# do something here
end
end
MyService.( valid_params )
Notes: we don't need any domain logic, that's veryuser specific and shouldn't be dictated by "my
framework"
class MyService def call(params)
return unless can?(current_user, Post, :new)
post = Post.new(author: current_user)
post.update_attributes(
params.require(:post).permit(:title)
)
if post.save
notify_current_user!
end
end
end
[ ] DIAGRAMS[x] 6 MEMES[x ] 2 BULLET POINT LISTS[ ] QUOTE FROM SOMEONE[ ] MORE DIAGRAMS[ ] TRUCKLOADS OF CODE (you wanted it)
class MyService def call(params)
return unless can?(current_user, Post, :new)
post = Post.new(author: current_user)
post.update_attributes(
params.require(:post).permit(:title)
)
if post.save
notify_current_user!
end
end
end
TESTit do
service = MyService.new
service.call( valid_params )
expect(Post.last).to be_persisted
end
HAPPY!
...THINKING...
SERVICE OBJECTS, REVISITED
[x] Encapsulation[x] Testing[ ] What to return?[ ] Validations extracted?[ ] Extendable
class MyService def call(params)
end
end
Is the problem the procedural* code design?
AND THAT'S TRB.
THANK YOU!
QUESTIONS?
OK, I GOT
A QUESTIONTHEN:
DO YOU WANTSOME CODE?
DO YOU WANTSOME CODE?
NO?
class Create < Trailblazer::Operation #
#
#
end
class BlogPost::Create < Trailblazer::Operation #
#
#
end
class Create < Trailblazer::Operation #
#
#
end
class Create < Trailblazer::Operation def process(params)
# sam's code here
end
end
Notes: not really extendable
class Create < Trailblazer::Operation def process(params)
return unless can?(current_user, Post, :new)
post = Post.new(author: current_user)
post.update_attributes(
params.require(:post).permit(:title)
)
if post.save
notify_current_user!
end
end
end
class Create < Trailblazer::Operation #
#
#
end
result = Create.()
result.success? #=> true
Notes:
Hooray, we have an API for service objects!
HOORAY, A
SERVICEOBJECT
API!
class Create < Trailblazer::Operation #
#
#
#
end
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
step :notify_current_user!
end
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
step :notify_current_user!
end
class Create < Trailblazer::Operation step :create_model!
#
#
#
#
end
class Create < Trailblazer::Operation step :create_model!
def create_model!(options, **)
end
end
CREATE MODELclass Create < Trailblazer::Operation step :create_model!
def create_model!(options, **)
options["model"] = BlogPost.new
end
end
result = Create.()
result.success? #=> true
result["model"] #=> #<BlogPost id:nil, ..>
VALIDATEclass Create < Trailblazer::Operation step :create_model!
step :validate!
def create_model!(options, **)
# ..
def validate!(options, params:, **)
# validate params
end
end
valid_params = { body: "Blogging's fun. #not" }
Create.( valid_params )
class Create < Trailblazer::Operation # ..
def validate!(options, params:, **)
params #=> { body: "Blogging's fun. #not" }
end
end
Notes: sending params into the op
class Create < Trailblazer::Operation # ..
def validate!(options, params:, **)
model = options["model"] # from the create_model! step...
if model.update_attributes(params)
true
else
false
end
end
end
class Create < Trailblazer::Operation # ..
def validate!(options, params:, model:, **)
#
#
if model.update_attributes(params)
true
else
false
end
end
end
#class Create < Trailblazer::Operation
# ..
def validate!(options, params:, model:, **)
if model.update_attributes(params)
true
else
false
end
end
#end
#class Create < Trailblazer::Operation
# ..
def validate!(options, params:, model:, **)
#
#
#
#
model.update_attributes(params)
end
#end
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
#
#
#
#
#
#
end
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
#def create_model!(options, **)
#def validate!(options, params:, **)
def save!(options, params:, model:, **)
true
end
end
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
step :notify!
#def create_model!(options, **)
#def validate!(options, params:, **)
#def save!(options, params:, model:, **)
def notify!(options, model:, **)
MyMailer.call(model)
end
end
HAPPYTIMES!
... AND WHEN
THINGSGO WRONG?
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
step :notify!
#def create_model!(options, **)
#def validate!(options, params:, **)
#def save!(options, params:, model:, **)
#def notify!(options, model:, **)
end
class Create < Trailblazer::Operation step :create_model!
step :validate! [XXX]
step :save!
step :notify!
#def create_model!(options, **)
#def validate!(options, params:, **)
#def save!(options, params:, model:, **)
#def notify!(options, model:, **)
end
#class Create < Trailblazer::Operation
# ..
def validate!(options, params:, model:, **)
model.update_attributes(params) #=> false
end
#end
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
step :notify!
failure :handle!
#..
end
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
step :notify!
failure :handle!
#..
def handle!(options, **)
options["error"] = "don't cry!"
end
end
result = Create.( { title: nil } )
result.success? #=> false
result["error"] = "don't cry!"
RAILWAYSROCK!
BUT ISN'T THAT
SUPERCOMPLEX?
DUDE.
Notes: do you find this more complex than this?
class MyService def call(params)
return unless can?(current_user, Post, :new)
post = Post.new(author: current_user)
if post.update_attributes(
params.require(:post).permit(:title)
)
unless notify_current_user!
if ...
else
end
end
end
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
step :notify!
failure :handle!
# ..
end
def create_model!(options, **)
def validate!( options, params:, **)
def save!( options, params:, model:, **)
def notify!( options, model:, **)
result = Create.( { title: nil } )
result.success? #=> false
result["error"] #=> "don't cry!"
result["model"] #=> #<BlogPost title: nil>
TESTit "fails with empty title" do
result = Create.( { title: nil } )
expect(result).to be_success
expect(result["error"]).to eq("don't cry!")
expect(result["model"]).to be_persisted
end
rspec-trailblazer
minitest-trailblazer
Notes:
validations still in model
class PostsController < ApplicationController def create
return unless can?(current_user, Post, :new)
post = Post.new(author: current_user)
if post.update_attributes(
params.require(:post).permit(:title))
notify_current_user!
else
render :new
end
end
end
class PostsController < ApplicationController def create
return unless can?(current_user, Post, :new)
#
#
#
result = BlogPost::Create.( params )
if result.failure?
render :new
end
end
end
AUTHORIZATIONdef create
return unless can?(current_user, Post, :new)
# ..
class Create < Trailblazer::Operation step :authorize!
#step :create_model!
#step :validate!
#step :save!
#step :notify!
#failure :handle!
# ..
end
class Create < Trailblazer::Operation step :authorize!
# ..
def authorize!(options, current_user:, **)
CouldCould.can?(current_user, Post, :new)
end
end
CURRENT WHAT?
def authorize!(options, current_user:, **)
CouldCould.can?(
current_user, # wtf?
Post, :new
)
end
class PostsController < ApplicationController def create
return unless can?(current_user, Post, :new)
result = BlogPost::Create.( params )
#
#
#
if result.failure?
render :new
end
end
end
class PostsController < ApplicationController def create
return unless can?(current_user, Post, :new)
result = BlogPost::Create.(
params,
"current_user" => current_user
)
if result.failure?
render :new
end
end
end
class PostsController < ApplicationController def create
#return unless can?(current_user, Post, :new)
#
result = BlogPost::Create.(
params,
"current_user" => current_user
)
if result.failure?
render :new
end
end
end
class PostsController < ApplicationController def create
result = BlogPost::Create.(
params,
"current_user" => current_user
)
if result.failure?
render :new
end
end
end
class PostsController < ApplicationController def create
run BlogPost::Create, "current_user" => current_user do
return
end
render :new
end
end
class PostsController < ApplicationController def create
run BlogPost::Create do
return
end
render :new
end
end
class PostsController < ApplicationController def create
run BlogPost::Create do |result|
return redirect_to blog_post_path(result["model"].id)
end
render :new
end
end
class PostsController < ApplicationController def create
run BlogPost::Create do |result|
return redirect_to blog_post_path(result["model"
end
render :new
end
end
DEPENDENCY INJECTION it "works with current_user" do
result = Create.(
valid_params,
"current_user" => User.find(1) )
# ..
end
class Create < Trailblazer::Operation step :authorize!
# ..
def authorize!(options, current_user:, **)
CouldCould.can?(current_user, Post, :new)
end
end
class Create < Trailblazer::Operation step MyAuth
# ..
class MyAuth def self.call(options, current_user:, **)
CouldCould.can?(current_user, Post, :new)
end
end
end
class Create < Trailblazer::Operation step MyAuthCallableSittingSomewhere
# ..
#class MyAuth
# def self.call(options, current_user:, **)
# CouldCould.can?(current_user, Post, :new)
# end
#end
end
DYI SUCKS
class Create < Trailblazer::Operation step Policy::CanCan( Post, :new )
# step :model!
# ..
end
VALIDATIONS:A STORY OF MANKIND
class Post < ActiveRecord::Base validates :title, presence:true
validates :body, presence:true
#after_save :notify_moderators!, if: :create?
end
module BlogPost module Contract class Create::Create < Reform::Form property :title
property :body
validates :title, presence:true
validates :body, presence:true
end
end
end
class Create < Trailblazer::Operation step Policy::CanCan( Post, :new )
step :model!
step :validate!
step :notify!
# ..
def model!(options, **)
def validate!(options, **)
# ..
end
class Create < Trailblazer::Operation step Policy::CanCan( Post, :new )
step :model!
step Contract::Build( constant: Contract::Create )
step Contract::Validate()
step Contract::Persist()
step :notify!
# ..
def model!(options, **)
# ..
end
BPMN
class Create < Trailblazer::Operation step :create_model!
step :validate!
step :save!
step :notify!
failure :handle!
# ..
end
result = Create.(
params,
"current_user" => ..
)
SAY GOODBYE TO
PROCEDURAL*PROGRAMMING
* AS WE KNOW IT.
Trailblazer is awesome! -- Someone
[ ] DIAGRAMS[x] 6 MEMES[xX] 2 BULLET POINT LISTS[x] QUOTE FROM SOMEONE[x] MORE DIAGRAMS[ ] TRUCKLOADS OF CODE (you wanted it)
@APOTONICK ❤
top related