8. The Dependency Rule
"...source code
dependencies can only
point inwards."
8 https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
10. This user knows too much
class User < ActiveRecord::Base
# ...
def password_required?
# Password is required if it is being set, but not for new records
if !persisted?
false
else
!password.nil? || !password_confirmation.nil?
end
end
end
10
https://github.com/plataformatec/devise/wiki/How-To:-Override-confirmations-so-users-can-pick-their-own-passwords-as-part-of-confirmation-activation
11. Interactors for the rescue
class EmailSignUp < Interactor
def call(request, response)
# ... validate data without password
# ... application rules through domain objects
# ... build response
end
end
class PasswordSignUp < Interactor
def call(request, response)
# ... validate data, password included
# ... application rules through domain objects
# ... build response
end
end
11
12. What is this?
def update
with_unconfirmed_confirmable do
if @confirmable.has_no_password?
@confirmable.attempt_set_password(params[:user])
if @confirmable.valid? and @confirmable.password_match?
do_confirm
else
do_show
@confirmable.errors.clear #so that we wont render :new
end
else
@confirmable.errors.add(:email, :password_already_set)
end
end
if !@confirmable.errors.empty?
self.resource = @confirmable
render 'devise/confirmations/new' #Change this if you don't have the
views on default path
end
end
12
https://github.com/plataformatec/devise/wiki/How-To:-Override-confirmations-so-users-can-pick-their-own-passwords-as-part-of-confirmation-activation
13. Cleaning it up!
class Controller
def update
@presenter = build_response
interactor.call(user_params, @presenter)
if @presenter.valid?
redirect_to dashboard_path
else
render :new
end
end
end
class SetPassword < Interactor
def call(request, response)
# ... validate data and fetch user
user.set_password(request[:password])
# ... save user and other application rules
end
end
class User
def set_password(new_password)
raise PasswordAlreadySetError unless encrypted_password.blank?
self.encrypted_password = encrypt(new_password)
end
end
13
14. Business Object
class User
def set_password(new_password)
raise PasswordAlreadySetError unless encrypted_password.blank?
self.encrypted_password = encrypt(new_password)
end
end
14
15. Use case
class SetPassword < Interactor
def call(request, response)
# ... validate data and fetch user
user.set_password(request[:password])
# ... save user and other application rules
end
end
15
16. Delivery mechanism
class Controller
def update
@presenter = build_response
interactor.call(user_params, @presenter)
if @presenter.valid?
redirect_to dashboard_path
else
render :new
end
end
end
Interface adapter
16
18. Wrap up
• Your application should be framework agnostic
• Your application should be infrastructure agnostic
• Web is a delivery mechanism (so is a rake task, the
console)
• Testing should be fast
18
20. And what about Hanami?
• Web delivery: Web::Controllers
• Use cases: Hanami::Interactor
• Infrastructure: Hanami::Repository
• Business objects: Hanami::Entity
20