13. SLIM MODEL
• Validação
• Relacionamentos
class
User
<
ActiveRecord::Base
has_one
:store
validates
:name,
presence:
true
!
accepts_nested_attributes_for
:store
end
class
Store
<
ActiveRecord::Base
belongs_to
:user
validates
:url,
presence:
true
end
14. PROBLEMAS
Mas O Que Isso
Significa?
• E se precisássemos de mais de
um controller para criar conta?
• Vários pontos de saída
• Acoplamento entre modelos
(user e store)
16. CODE SMELLS
def
create
@user
=
User.new(user_params)
@store
=
@user.build_store(store_params)
!
• Divergent change
• This smell refers to
making unrelated
changes in the same
location.
• Feature Envy
• a method that seems
more interested in a
class other than the
one it actually is in
captcha
=
CaptchaQuestion.find(captcha_id)
unless
captcha.valid?(captcha_answer)
flash[:error]
=
'Captcha
answer
is
invalid'
render
:new
and
return
end
!
ActiveRecord::Base.transaction
do
@user.save!
@store.save!
@user.store
=
@store
end
!
IpLogger.log(request.remote_ip)
SignupEmail.deliver(@user)
!
redirect_to
accounts_path
!
rescue
ActiveRecord::RecordInvalid
render
:new
end
18. SANDI RULES
def
create
@user
=
User.new(user_params)
@store
=
@user.build_store(store_params)
!
• Classes can be no longer
than one hundred lines of
code.
• Methods can be no longer
than five lines of code.
• Pass no more than four
parameters into a
method.
• Controllers can
instantiate only one
object.
captcha
=
CaptchaQuestion.find(captcha_id)
unless
captcha.valid?(captcha_answer)
flash[:error]
=
'Captcha
answer
is
invalid'
render
:new
and
return
end
!
ActiveRecord::Base.transaction
do
@user.save!
@store.save!
@user.store
=
@store
end
!
IpLogger.log(request.remote_ip)
SignupEmail.deliver(@user)
!
redirect_to
accounts_path
!
rescue
ActiveRecord::RecordInvalid
render
:new
end
22. • Classes can be no longer
than one hundred lines
of code.
• Methods can be no
longer than five lines of
code.
• Pass no more than four
parameters into a
method.
• Controllers can
instantiate only one
object.
23. FAT MODEL
class
User
<
ActiveRecord::Base
attr_accessor
:remote_ip,
:captcha_id,
:captcha_answer
!
has_one
:store
!
• Criação de Store
• Validação (humano)
• Database stuff
• Auditoria (IP)
• Email
validates
:name,
presence:
true
validate
:ensure_captcha_answered,
on:
:create
accepts_nested_attributes_for
:store
!
after_create
:deliver_email
after_create
:log_ip
!
protected
!
def
deliver_email
SignupEmail.deliver(@user)
end
!
def
log_ip
IpLogger.log(self.remote_ip)
end
!
def
ensure_captcha_answered
captcha
=
CaptchaQuestion.find(self.captcha_id)
!
unless
captcha.valid?(self.captcha_answer)
errors.add(:captcha_answer,
:invalid)
end
end
end
24. CODE SMELLS
•
class
User
<
ActiveRecord::Base
attr_accessor
:remote_ip,
:captcha_id,
:captcha_answer
!
has_one
:store
!
Divergent change
• This smell refers to making
unrelated changes in the
same location.
• Feature Envy
• a method that seems more
interested in a class other
than the one it actually is in
• Inappropriate Intimacy
•
too much intimate
knowledge of another
class or method's inner
workings, inner data, etc.
validates
:name,
presence:
true
validate
:ensure_captcha_answered,
on:
:create
accepts_nested_attributes_for
:store
!
after_create
:deliver_email
after_create
:log_ip
!
protected
!
def
deliver_email
SignupEmail.deliver(@user)
end
!
def
log_ip
IpLogger.log(self.remote_ip)
end
!
def
ensure_captcha_answered
captcha
=
CaptchaQuestion.find(self.captcha_id)
!
unless
captcha.valid?(self.captcha_answer)
errors.add(:captcha_answer,
:invalid)
end
end
end
25. ACTIVE RECORD
Regras De Negócio No
Active Record?
• Precisa do ActiveRecord (specs)
• Acesso a métodos de baixo
nível
• update_attributes
• A instância valida a sí mesma
• Difícil de testar
28. NOVOS BALDES
• Novas camadas
• Melhor separação
de concerns
• Por muito tempo o
Rails não
estimulava isso
29. FORM OBJECTS
•
• Realiza validações
• Dispara Callbacks
• app/forms
Delega persistência
module
Form
extend
ActiveSupport::Concern
include
ActiveModel::Model
include
DelegateAccessors
!
included
do
define_model_callbacks
:persist
end
!
def
submit
return
false
unless
valid?
run_callbacks(:persist)
{
persist!
}
true
end
!
def
transaction(&block)
ActiveRecord::Base.transaction(&block)
end
end
30. FORM: O BÁSICO
• Provê accessors
• Delega
responsabilidades
• Infra de callbacks
• Realiza validações
• Inclusive
customizadas
class
AccountForm
include
Form
!
attr_accessor
:captcha_id,
:captcha_answer
!
delegate_accessors
:name,
:password,
:email,
to:
:user
!
delegate_accessors
:name,
:url,
to:
:store,
prefix:
true
!
validates
:captcha_answer,
captcha:
true
validates
:name,
:store_url,
presence:
true
end
31. FORM: ATRIBUTOS
• Alguns são da class
• Alguns são
attr_accessor
:captcha_id,
:captcha_answer
delegados
•
!
delegate_accessors
!
delegate_accessors
:name,
:password,
:email,
to:
:user
delegate_accessors
:name,
:url,
to:
:store,
prefix:
true
32. FORM: VALIDAÇÃO
• Fácil de compor em
outros FormObjects
• Não modifica a
lógica do Form
Object
• Pode ser testada em
isolamento
#
account_form.rb
validates
:captcha_answer,
captcha:
true
!
#
captcha_validator.rb
class
CaptchaValidator
def
validate_each(r,
attr,
val)
captcha
=
CaptchaQuestion.find(r)
!
unless
captcha.valid?(val)
r.errors.add(attr,
:invalid)
end
end
end
33. FORM: CALLBACKS
• Dispara callbacks
• Callbacks
implementados em
classe a parte
• Reutilizáveis
• Pode ser testado em
isolamento
#
account_form.rb
after_persist
SendSignupEmail,
LogIp
!
!
!
class
SendSignupEmail
class
<<
self
def
after_persist(form)
SignupEmail.deliver(form.user)
end
end
end
!
class
LogIp
class
<<
self
def
after_persist(form)
IpLogger.log(form.remote_ip)
end
end
end
34. #
account_form.rb
FORM: PERSISTÊNCIA
!
protected
!
• Delega para os
models
• Precisa do
ActiveRecord :(
def
store
@store
||=
Store.new
end
!
def
user
@user
||=
User.new
end
!
def
persist!
transaction
do
user.save
store.save
user.store
=
store
end
end
36. SLIM MODEL
• Apenas
relacionamentos
• Sem validações
• Sem callbacks
class
Store
<
ActiveRecord::Base
belongs_to
:user
end
!
class
User
<
ActiveRecord::Base
has_one
:store
end
37. CODE SMELL
• Divergent change
• This smell refers to
making unrelated
changes in the same
location.
def
persist!
transaction
do
user.save
store.save
user.store
=
store
end
end
41. REFORM
• Desacopla
persistência de
lógica de domínio
• Nesting
• Relacionamentos
• Coerção (usando o
Virtus)
@form.save
do
|data,
nested|
u
=
User.create(nested[:user])
s
=
Store.create(nested[:store])
u.stores
=
s
end