This document summarizes a presentation about saying goodbye to procedural programming and introducing the Trailblazer framework. Key points include:
- Trailblazer extends the basic MVC pattern with new abstractions like operations and steps to encapsulate business logic in a declarative way.
- Operations can be tested independently by passing parameters and validating outputs. Failure handling is also supported.
- The framework provides dependency injection so business logic can access objects like the current user without direct coupling.
- Common patterns like validation and authorization can be abstracted into reusable steps.
- While more complex initially, the document argues Trailblazer results in cleaner, more modular code compared to nested conditional logic in traditional controllers and services
6. [ ] DIAGRAMS
[ ] 6 MEMES
[ ] 2 BULLET POINT LISTS
[ ] QUOTE FROM SOMEONE
[ ] MORE DIAGRAMS
[ ] TRUCKLOADS OF CODE (you wanted it)
7. [ ] DIAGRAMS
[ ] 6 MEMES
[x ] 2 BULLET POINT LISTS
[ ] QUOTE FROM SOMEONE
[ ] MORE DIAGRAMS
[ ] TRUCKLOADS OF CODE (you wanted it)
8.
9.
10.
11.
12. class Post < ActiveRecord::Base
validates :body, presence:true
validates :author, presence:true
after_save :notify_moderators!, if: :create?
end
13.
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
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24. Let's not talk
about persistence!
Let's not talk
about business logic!
Let's not talk
about views!
31. 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
32. 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
33. it do
controller = Controller.new
controller.create( valid_params )
expect(Post.last).to be_persisted
end
34.
35. 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
49. Notes: we don't need any domain logic, that's very
user specific and shouldn't be dictated by "my
framework"
50. 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
51.
52. [ ] DIAGRAMS
[x] 6 MEMES
[x ] 2 BULLET POINT LISTS
[ ] QUOTE FROM SOMEONE
[ ] MORE DIAGRAMS
[ ] TRUCKLOADS OF CODE (you wanted it)
53. 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
54. TEST
it do
service = MyService.new
service.call( valid_params )
expect(Post.last).to be_persisted
end
57. SERVICE OBJECTS, REVISITED
[x] Encapsulation
[x] Testing
[ ] What to return?
[ ] Validations extracted?
[ ] Extendable
class MyService
def call(params)
end
end
74. 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
75. class Create < Trailblazer::Operation
#
#
#
end
result = Create.()
result.success? #=> true
89. 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
90. class Create < Trailblazer::Operation
# ..
def validate!(options, params:, model:, **)
#
#
if model.update_attributes(params)
true
else
false
end
end
end
91.
92.
93.
94. #class Create < Trailblazer::Operation
# ..
def validate!(options, params:, model:, **)
if model.update_attributes(params)
true
else
false
end
end
#end
122. 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
123. class Create < Trailblazer::Operation
step :create_model!
step :validate!
step :save!
step :notify!
failure :handle!
# ..
end
126. TEST
it "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
131. 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
132. class PostsController < ApplicationController
def create
return unless can?(current_user, Post, :new)
#
#
#
result = BlogPost::Create.( params )
if result.failure?
render :new
end
end
end
134. class Create < Trailblazer::Operation
step :authorize!
#step :create_model!
#step :validate!
#step :save!
#step :notify!
#failure :handle!
# ..
end
135. class Create < Trailblazer::Operation
step :authorize!
# ..
def authorize!(options, current_user:, **)
CouldCould.can?(current_user, Post, :new)
end
end
138. class PostsController < ApplicationController
def create
return unless can?(current_user, Post, :new)
result = BlogPost::Create.( params )
#
#
#
if result.failure?
render :new
end
end
end
139. 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
140. 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
141. class PostsController < ApplicationController
def create
result = BlogPost::Create.(
params,
"current_user" => current_user
)
if result.failure?
render :new
end
end
end
142. class PostsController < ApplicationController
def create
run BlogPost::Create, "current_user" => current_user do
return
end
render :new
end
end
143. class PostsController < ApplicationController
def create
run BlogPost::Create do
return
end
render :new
end
end
144. class PostsController < ApplicationController
def create
run BlogPost::Create do |result|
return redirect_to blog_post_path(result["model"].id)
end
render :new
end
end
145. class PostsController < ApplicationController
def create
run BlogPost::Create do |result|
return redirect_to blog_post_path(result["model"
end
render :new
end
end
146. DEPENDENCY INJECTION
it "works with current_user" do
result = Create.(
valid_params,
"current_user" => User.find(1) )
# ..
end
147.
148.
149. class Create < Trailblazer::Operation
step :authorize!
# ..
def authorize!(options, current_user:, **)
CouldCould.can?(current_user, Post, :new)
end
end
150. class Create < Trailblazer::Operation
step MyAuth
# ..
class MyAuth
def self.call(options, current_user:, **)
CouldCould.can?(current_user, Post, :new)
end
end
end
151. class Create < Trailblazer::Operation
step MyAuthCallableSittingSomewhere
# ..
#class MyAuth
# def self.call(options, current_user:, **)
# CouldCould.can?(current_user, Post, :new)
# end
#end
end