From The If Jungle to A
Civilised Railway Station
By Botond Orban
About Me
Botond Orban Enthusiast IT Guy, Architect 

Enthusiastic about Ruby

https://github.com/orbanbotond

@orbanbotond
The Origins
The PC made in Ukraine
Code can be written
in a better way!
Railway Oriented Development
With 3 libraries in Parallel!
-classic if jungle
-dry transactions
-monad do notation
-trailblazer operations
def create
user = User.create user_params[:user]
if user.valid?
package = Package.create package_params[:user]
if package.valid?
user.package = package
user.save
SmsService.send_registration_msg(user, package)
EmailService.send_registration_msg(user, package)
SystemNotifierService.send_registration_msg(user,
package)
else
render ...
end
else
render ...
end
end
True If Jungle
Cyclomatic complexity: 2
def create
user = User.create user_params[:user]
if user.valid?
package = Package.create package_params[:user]
if package.valid?
package.user = user
package.save
if params[:coupon].present?
if coupon = Coupon.exists? params[:coupon]
discount = Discount.create params[:coupon]
package.discount = discount
package.save
else
render …
end
end
SmsService.send_registration_msg(user, package)
EmailService.send_registration_msg(user, package)
SystemNotifierService.send_registration_msg(user, package)
else
render ...
end
else
render ...
end
end
True If Jungle
Cyclomatic complexity: 4
True If Jungle
def create
user = User.create user_params[:user]
if user.valid?
package = Package.create package_params[:user]
if package.valid?
package.user = user
package.save
if params[:coupon].present?
if Coupon.exists? params[:coupon]
discount = Discount.create params[:coupon]
if discount.allowsUser? user
package.discount = discount
package.save
else
render ...
end
else
render ...
end
end
SmsService.send_registration_msg(user, package)
EmailService.send_registration_msg(user, package)
SystemNotifierService.send_registration_msg(user, package)
else
render ...
end
else
render ...
end
end
Cyclomatic complexity: 5
Example 1
f(x) = ax+b
No Library
If Jungle
If Jungle Implementation
Specs:
f(a,b) = a+b, a is infinite
context 'add' do
subject { add.call params: params }


context 'negative cases' do
context 'params infinite' do
let(:params) { [(1.0/0.0), 2] }
specify 'Be a failure with a proper error
message' do
expect(subject[:validation]).to eq 'must
be a real number'
end
end
end
add = ->(params:) do
return { validation: 'must be a real
number' } if params.any?{|x|x.infinite?}
end
Code:
If Jungle Implementation
f(a,b) = a+b, a is infinite
Specs:
context 'add' do
subject { add.call params: params }
let(:params) { [2,3,4] }
context 'negative cases' do …
context 'positive cases' do
specify 'Be a success with the proper correct
output' do
expect(subject[:operation_result]).to
eq(params.reduce(0) { |acc, x| acc + x })
end
end
end
If Jungle Implementation
f(a,b) = a+b,
Code:
add = ->(params:) do
return { validation: 'must be a real
number' } if params.any?{|x|x.infinite?}
result = params.reduce(0) { |acc, x| acc
+ x }
return { operation_result: result }
end
If Jungle Implementation
f(a,b) = a+b,
linear_function = ->(params:) do
result = multiply.call(params: params[-2..-1])
if(result[:operation_result])
return add.call(params:
[result[:operation_result], params[0]])
else
return result
end
end
rspec spec/railway_oriented_development/if_jungle_spec.rb
.......
7 examples, 0 failures
If Jungle Implementation
f(a,b,x) = a.x+b, Linear function
Building Blocks
Add Operation

-Guard Condition

-Business Logic: +

Multiply Operation

-Guard Condition

-Business Logic: *

LinearFunction

-Delegation

-Conditional

-Delegation

-Return a Result
If Jungle Implementation
1st library
Dry Transactions
Specs:
context 'add' do
subject { DryTransactions::Add.new.call params }


context 'negative cases' do
context 'params infinite' do
let(:params) { {params:[(1.0/0.0), 2]} }
specify 'Be a failure with a proper error
message' do
expect(subject).to be_failure
expect(subject.failure).to eq 'must be a
real number'
end
end
end
Dry Transactions
f(a,b) = a+b, a is infinite
Code:
module DryTransactions
class Add
include Dry::Transaction
step :validate
step :add
private
…
…
Dry Transactions
f(a,b) = a+b, a is infinite
Railway Oriented Approach
Code:
module DryTransactions
class Add
…
private
def validate(input)
return Failure('must be a real number') unless input.all?{|x|
x.finite?}
Success(input)
end

def add(input)
ret = input.reduce(0) { |acc, x| acc + x }
Success(ret)
end
…
Dry Transactions
f(a,b) = a+b, a is infinite
class LinearOperation
include Dry::Transaction
# a*x
step :multiply
# previous_result + b
step :assembling_partial_results
private
…
end
Dry Transactions
f(a, b, x) = a.x+b, Linear Function
class ComplexOperation
include Dry::Transaction
…
def multiply(input)
partialValue = Multiply.new.call(params: [input[:params]
[1], input[:params][2]])
partialValue.bind do |value|
Success(input.merge(multiplication_result:
partialValue.value!))
end
end
def assembling_partial_results(input)
Add.new.call( params: [input[:params][0],
input[:multiplication_result]])
end
end
rspec spec/railway_oriented_development/dry_transactions_spec.rb
.......
7 examples, 0 failures
Dry Transactions
f(a, b, x) = a.x+b, a is infinite
Dry Transaction Implementation
Add Operation

-Guard Step

-Business Logic Step: +

Multiply Operation

-Guard Step

-Business Step: *

LinearFunction

-Multiply Step

-Assemble Step

Note: 

-no conditional compared to the If Jungle Solution!

-linear execution by enlisting the steps!
2rd library
Monad Do Notation
Specs:
context 'add' do
subject { MonadDoNotation::Add.new.call params }


context 'negative cases' do
context 'params infinite' do
let(:params) { [(1.0/0.0), 2] }
specify 'Be a failure with a proper error
message' do
expect(subject).to be_failure
expect(subject.failure[:validation]).to eq
'must be a real number'
end
end
end
Monad Do Notation
f(a,b) = a+b, a is infinite
Code:
class Add
include Dry::Monads::Result::Mixin
include Dry::Monads::Do::All
def call(arguments)
validation_result = yield validate(arguments)
operation_result = yield add(arguments)
Success validation_result.merge( operation_result)
end
…
end
Monad Do Notation
f(a,b) = a+b, a is infinite
Code:
class Add
…
def validate(input)
return Failure(validation: 'must be a real
number') unless input.all?{|x|x.finite?}
Success(validation: :ok)
end
def add(input)
ret = input.reduce(0) { |acc, x| acc + x }
Success(operation_result: ret)
end
end
…
Monad Do Notation
f(a,b) = a+b, a is infinite
class LinearOperation
include Dry::Monads::Result::Mixin
include Dry::Monads::Do::All
def call(input)
multiplication = yield
multiply(input[-2..-1])
addition = yield add([input[0],
multiplication[:operation_result]])
Success(addition)
end
private …
Monad Do Notation
f(a, b, x) = a.x+b,
class LinearOperation
include Dry::Monads::Result::Mixin
include Dry::Monads::Do::All
…
private
def multiply(args)
Multiply.new.call args
end
def add(args)
Add.new.call args
end
end
rspec spec/railway_oriented_development/monad_do_notation_spec.rb
.......
7 examples, 0 failures
Monad Do Notation
f(a, b, x) = a.x+b,
Add Operation

-Validate Method

-Business Logic Method: +

Multiply Operation

-Validate Method

-Business Logic Method: *

LinearFunction

-Multiply Method

-Add Method Step

Note: 

-no conditional compared to the If Jungle Solution!

-linear execution!

-pure Ruby! *****

Monad Do Notation Implementation
3rd library
Trailblazer Operations
Specs:
context 'add' do
subject { TrailblazerOperations::Add.call params: params }


context 'negative cases' do
context 'params infinite' do
let(:params) { [(1.0/0.0), 2] }
specify 'Be a failure with a proper error message' do
expect(subject).to be_failure
expect(subject[:validation]).to eq 'must be a real
number'
end
end
end
Trailblazer Operations
f(a,b) = a+b, a is infinite
Code:
module TrailblazerOperations
class Add < Trailblazer::Operation
step :validate
step :add
private
…
end
End
Trailblazer Operations
f(a,b) = a+b, a is infinite
Railway Oriented Approach
Code:
module TrailblazerOperations
class Add < Trailblazer::Operation
…
private
def validate(options, params:)
unless params.all?{|x|x.finite?}
options[:validation] = 'must be a real number'
return Railway.fail!
end
Railway.pass!
end
def add(options, params:, **rest)
ret = params.reduce(0) { |acc, x| acc + x }
options[:operation_result] = ret
end
end
end
end
Code:
class LinearOperation < Trailblazer::Operation
step Nested( Multiply,
input: -> (options, params:, **) do
options.merge params: params[-2..-1]
end
)
step Nested( Add,
input: -> (options, params:, **) do
options.to_hash.except(:operation_result).merge
params: [params[0], options[:operation_result]]
end
)
end
rspec spec/railway_oriented_development/trailblazer_operations_spec.rb
.......
7 examples, 0 failures
Trailblazer Operations
f(a, b, x) = a.x+b,
Add Operation

-Validate Step

-Business Logic Step: +

Multiply Operation

-Validate Step

-Business Logic Step: *

LinearFunction

-Delegates to the Multiply Operation by Nesting

-Delegates to the Add Operation by Nesting

Note:

-no conditional compared to the If Jungle Solution!

-linear execution!

-DSL for reuse!

Trailblazer Implementation
Dry-Transaction Monad Do Notation Trailblazer
Steps Steps
Ruby Code Wrapped
With Yield
Steps
Code Reuse Ruby Call
Ruby Code Wrapped
With Yield
DSL for other
Operation Reuse!
True If Jungle
def create
user = User.create user_params[:user]
if user.valid?
package = Package.create package_params[:user]
if package.valid?
package.user = user
package.save
if params[:coupon].present?
if Coupon.exists? params[:coupon]
discount = Discount.create params[:coupon]
if discount.allowsUser? user
package.discount = discount
package.save
else
render ...
end
else
render ...
end
end
SmsService.send_registration_msg(user, package)
EmailService.send_registration_msg(user, package)
SystemNotifierService.send_registration_msg(user, package)
else
render ...
end
else
render ...
end
end
Cyclomatic complexity: 5
Railway Oriented Development
Cyclomatic complexity: 1-2
class Add < Trailblazer::Operation
step :persist_user
step :persist_package
step :add_coupon_based_discount
step :notify_about_registration
...
end
Railway Oriented Development
Cyclomatic complexity: 1-2
class Add < Trailblazer::Operation
step :persist_user
failure :log_user_persistance
step :persist_package
failure :log_package_persistance
step :add_coupon_based_discount
failure :log_discount_creation
step :notify_about_registration
...
end
Railway Oriented Development
Cyclomatic complexity: 1-2
class Add < Trailblazer::Operation
step :persist_user
failure :log_user_persistance
step :persist_package
failure :log_package_persistance
step :add_coupon_based_discount
failure :log_discount_creation
step :notify_about_registration
step :notify_facebook_friends
…
end
Contract for every input (entity & other use case)



Basement:

-Operations for CRUD



Crud (and Other) Reuse (DRY):

-Operations for Onboarding

-Operations for Admin

-Operations for handling Business Use Cases
Trailblazer Operations & Contracts (Reform)
My Best Practice
Thank you ;)
Botond Orban Enthusiast IT Guy, Architect 

Enthusiastic about Ruby

https://github.com/orbanbotond

@orbanbotond

The Trailblazer Ride from the If Jungle into a Civilised Railway Station - Orban Botond (ENG) | Ruby Meditation 27

  • 1.
    From The IfJungle to A Civilised Railway Station By Botond Orban
  • 2.
    About Me Botond OrbanEnthusiast IT Guy, Architect Enthusiastic about Ruby
 https://github.com/orbanbotond
 @orbanbotond
  • 3.
    The Origins The PCmade in Ukraine
  • 4.
    Code can bewritten in a better way!
  • 5.
    Railway Oriented Development With3 libraries in Parallel! -classic if jungle -dry transactions -monad do notation -trailblazer operations
  • 6.
    def create user =User.create user_params[:user] if user.valid? package = Package.create package_params[:user] if package.valid? user.package = package user.save SmsService.send_registration_msg(user, package) EmailService.send_registration_msg(user, package) SystemNotifierService.send_registration_msg(user, package) else render ... end else render ... end end True If Jungle Cyclomatic complexity: 2
  • 7.
    def create user =User.create user_params[:user] if user.valid? package = Package.create package_params[:user] if package.valid? package.user = user package.save if params[:coupon].present? if coupon = Coupon.exists? params[:coupon] discount = Discount.create params[:coupon] package.discount = discount package.save else render … end end SmsService.send_registration_msg(user, package) EmailService.send_registration_msg(user, package) SystemNotifierService.send_registration_msg(user, package) else render ... end else render ... end end True If Jungle Cyclomatic complexity: 4
  • 8.
    True If Jungle defcreate user = User.create user_params[:user] if user.valid? package = Package.create package_params[:user] if package.valid? package.user = user package.save if params[:coupon].present? if Coupon.exists? params[:coupon] discount = Discount.create params[:coupon] if discount.allowsUser? user package.discount = discount package.save else render ... end else render ... end end SmsService.send_registration_msg(user, package) EmailService.send_registration_msg(user, package) SystemNotifierService.send_registration_msg(user, package) else render ... end else render ... end end Cyclomatic complexity: 5
  • 9.
  • 10.
  • 11.
    If Jungle Implementation Specs: f(a,b)= a+b, a is infinite context 'add' do subject { add.call params: params } 
 context 'negative cases' do context 'params infinite' do let(:params) { [(1.0/0.0), 2] } specify 'Be a failure with a proper error message' do expect(subject[:validation]).to eq 'must be a real number' end end end
  • 12.
    add = ->(params:)do return { validation: 'must be a real number' } if params.any?{|x|x.infinite?} end Code: If Jungle Implementation f(a,b) = a+b, a is infinite
  • 13.
    Specs: context 'add' do subject{ add.call params: params } let(:params) { [2,3,4] } context 'negative cases' do … context 'positive cases' do specify 'Be a success with the proper correct output' do expect(subject[:operation_result]).to eq(params.reduce(0) { |acc, x| acc + x }) end end end If Jungle Implementation f(a,b) = a+b,
  • 14.
    Code: add = ->(params:)do return { validation: 'must be a real number' } if params.any?{|x|x.infinite?} result = params.reduce(0) { |acc, x| acc + x } return { operation_result: result } end If Jungle Implementation f(a,b) = a+b,
  • 15.
    linear_function = ->(params:)do result = multiply.call(params: params[-2..-1]) if(result[:operation_result]) return add.call(params: [result[:operation_result], params[0]]) else return result end end rspec spec/railway_oriented_development/if_jungle_spec.rb ....... 7 examples, 0 failures If Jungle Implementation f(a,b,x) = a.x+b, Linear function
  • 16.
    Building Blocks Add Operation -GuardCondition -Business Logic: + Multiply Operation -Guard Condition -Business Logic: * LinearFunction -Delegation -Conditional -Delegation -Return a Result If Jungle Implementation
  • 17.
  • 18.
    Specs: context 'add' do subject{ DryTransactions::Add.new.call params } 
 context 'negative cases' do context 'params infinite' do let(:params) { {params:[(1.0/0.0), 2]} } specify 'Be a failure with a proper error message' do expect(subject).to be_failure expect(subject.failure).to eq 'must be a real number' end end end Dry Transactions f(a,b) = a+b, a is infinite
  • 19.
    Code: module DryTransactions class Add includeDry::Transaction step :validate step :add private … … Dry Transactions f(a,b) = a+b, a is infinite
  • 20.
  • 21.
    Code: module DryTransactions class Add … private defvalidate(input) return Failure('must be a real number') unless input.all?{|x| x.finite?} Success(input) end
 def add(input) ret = input.reduce(0) { |acc, x| acc + x } Success(ret) end … Dry Transactions f(a,b) = a+b, a is infinite
  • 22.
    class LinearOperation include Dry::Transaction #a*x step :multiply # previous_result + b step :assembling_partial_results private … end Dry Transactions f(a, b, x) = a.x+b, Linear Function
  • 23.
    class ComplexOperation include Dry::Transaction … defmultiply(input) partialValue = Multiply.new.call(params: [input[:params] [1], input[:params][2]]) partialValue.bind do |value| Success(input.merge(multiplication_result: partialValue.value!)) end end def assembling_partial_results(input) Add.new.call( params: [input[:params][0], input[:multiplication_result]]) end end rspec spec/railway_oriented_development/dry_transactions_spec.rb ....... 7 examples, 0 failures Dry Transactions f(a, b, x) = a.x+b, a is infinite
  • 24.
    Dry Transaction Implementation AddOperation -Guard Step -Business Logic Step: + Multiply Operation -Guard Step -Business Step: * LinearFunction -Multiply Step -Assemble Step Note: -no conditional compared to the If Jungle Solution! -linear execution by enlisting the steps!
  • 25.
  • 26.
    Specs: context 'add' do subject{ MonadDoNotation::Add.new.call params } 
 context 'negative cases' do context 'params infinite' do let(:params) { [(1.0/0.0), 2] } specify 'Be a failure with a proper error message' do expect(subject).to be_failure expect(subject.failure[:validation]).to eq 'must be a real number' end end end Monad Do Notation f(a,b) = a+b, a is infinite
  • 27.
    Code: class Add include Dry::Monads::Result::Mixin includeDry::Monads::Do::All def call(arguments) validation_result = yield validate(arguments) operation_result = yield add(arguments) Success validation_result.merge( operation_result) end … end Monad Do Notation f(a,b) = a+b, a is infinite
  • 28.
    Code: class Add … def validate(input) returnFailure(validation: 'must be a real number') unless input.all?{|x|x.finite?} Success(validation: :ok) end def add(input) ret = input.reduce(0) { |acc, x| acc + x } Success(operation_result: ret) end end … Monad Do Notation f(a,b) = a+b, a is infinite
  • 29.
    class LinearOperation include Dry::Monads::Result::Mixin includeDry::Monads::Do::All def call(input) multiplication = yield multiply(input[-2..-1]) addition = yield add([input[0], multiplication[:operation_result]]) Success(addition) end private … Monad Do Notation f(a, b, x) = a.x+b,
  • 30.
    class LinearOperation include Dry::Monads::Result::Mixin includeDry::Monads::Do::All … private def multiply(args) Multiply.new.call args end def add(args) Add.new.call args end end rspec spec/railway_oriented_development/monad_do_notation_spec.rb ....... 7 examples, 0 failures Monad Do Notation f(a, b, x) = a.x+b,
  • 31.
    Add Operation -Validate Method -BusinessLogic Method: + Multiply Operation -Validate Method -Business Logic Method: * LinearFunction -Multiply Method -Add Method Step Note: -no conditional compared to the If Jungle Solution! -linear execution! -pure Ruby! ***** Monad Do Notation Implementation
  • 32.
  • 33.
    Specs: context 'add' do subject{ TrailblazerOperations::Add.call params: params } 
 context 'negative cases' do context 'params infinite' do let(:params) { [(1.0/0.0), 2] } specify 'Be a failure with a proper error message' do expect(subject).to be_failure expect(subject[:validation]).to eq 'must be a real number' end end end Trailblazer Operations f(a,b) = a+b, a is infinite
  • 34.
    Code: module TrailblazerOperations class Add< Trailblazer::Operation step :validate step :add private … end End Trailblazer Operations f(a,b) = a+b, a is infinite
  • 35.
  • 36.
    Code: module TrailblazerOperations class Add< Trailblazer::Operation … private def validate(options, params:) unless params.all?{|x|x.finite?} options[:validation] = 'must be a real number' return Railway.fail! end Railway.pass! end def add(options, params:, **rest) ret = params.reduce(0) { |acc, x| acc + x } options[:operation_result] = ret end end end end
  • 37.
    Code: class LinearOperation <Trailblazer::Operation step Nested( Multiply, input: -> (options, params:, **) do options.merge params: params[-2..-1] end ) step Nested( Add, input: -> (options, params:, **) do options.to_hash.except(:operation_result).merge params: [params[0], options[:operation_result]] end ) end rspec spec/railway_oriented_development/trailblazer_operations_spec.rb ....... 7 examples, 0 failures Trailblazer Operations f(a, b, x) = a.x+b,
  • 38.
    Add Operation -Validate Step -BusinessLogic Step: + Multiply Operation -Validate Step -Business Logic Step: * LinearFunction -Delegates to the Multiply Operation by Nesting -Delegates to the Add Operation by Nesting Note: -no conditional compared to the If Jungle Solution! -linear execution! -DSL for reuse! Trailblazer Implementation
  • 39.
    Dry-Transaction Monad DoNotation Trailblazer Steps Steps Ruby Code Wrapped With Yield Steps Code Reuse Ruby Call Ruby Code Wrapped With Yield DSL for other Operation Reuse!
  • 40.
    True If Jungle defcreate user = User.create user_params[:user] if user.valid? package = Package.create package_params[:user] if package.valid? package.user = user package.save if params[:coupon].present? if Coupon.exists? params[:coupon] discount = Discount.create params[:coupon] if discount.allowsUser? user package.discount = discount package.save else render ... end else render ... end end SmsService.send_registration_msg(user, package) EmailService.send_registration_msg(user, package) SystemNotifierService.send_registration_msg(user, package) else render ... end else render ... end end Cyclomatic complexity: 5
  • 41.
    Railway Oriented Development Cyclomaticcomplexity: 1-2 class Add < Trailblazer::Operation step :persist_user step :persist_package step :add_coupon_based_discount step :notify_about_registration ... end
  • 42.
    Railway Oriented Development Cyclomaticcomplexity: 1-2 class Add < Trailblazer::Operation step :persist_user failure :log_user_persistance step :persist_package failure :log_package_persistance step :add_coupon_based_discount failure :log_discount_creation step :notify_about_registration ... end
  • 43.
    Railway Oriented Development Cyclomaticcomplexity: 1-2 class Add < Trailblazer::Operation step :persist_user failure :log_user_persistance step :persist_package failure :log_package_persistance step :add_coupon_based_discount failure :log_discount_creation step :notify_about_registration step :notify_facebook_friends … end
  • 44.
    Contract for everyinput (entity & other use case)
 
 Basement:
 -Operations for CRUD 
 Crud (and Other) Reuse (DRY): -Operations for Onboarding -Operations for Admin -Operations for handling Business Use Cases Trailblazer Operations & Contracts (Reform) My Best Practice
  • 49.
    Thank you ;) BotondOrban Enthusiast IT Guy, Architect Enthusiastic about Ruby
 https://github.com/orbanbotond
 @orbanbotond