Say Goodbye to Procedural Programming - Nick Sutterer

Preview:

Citation preview

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 ❤