SlideShare une entreprise Scribd logo
1  sur  142
Télécharger pour lire hors ligne
Thanks for making it!
The magic hook
Event Form
Repetitive
Much better!
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
Datetime fields
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
event =
Event.new(
name: "Ruby On Ice",
location: "Tegernsee",
date: "24.02.2019",
crew_arrives_at: "6:45",
performers_arrive_at: "9:30",
open_at: "9:30",
starts_at: "10:00",
ends_at: "16:00"
)
event.valid?
#<Event:
name: "Ruby On Ice",
location: "Tegernsee",
date: Sun, 24 Feb 2019,
crew_arrives_at: Sun, 24 Feb 2019 6:45:00,
performers_arrive_at: Sun, 24 Feb 2019 9:00:00,
open_at: Sun, 24 Feb 2019 9:00:00,
starts_at: Sun, 24 Feb 2019 9:30:00,
ends_at: Sun, 24 Feb 2019 20:00:00>
It works!
let(:event) do
build :event,
ends_at: Time.zone.local(2042, 1, 1, 15, 45)
end
let(:event) do
build :event,
ends_at: Time.zone.local(2042, 1, 1, 15, 45)
end
let(:event) do
build :event,
ends_at: Time.zone.local(2042, 1, 1, 15, 45)
end
it "works" do
p event.ends_at # Wed, 01 Jan 2042 15:45:00
event.save!
p event.ends_at # Sun, 24 Feb 2019 15:45:00
end
Have fun debugging!
let(:event) do
create :event,
ends_at: Time.zone.local(2042, 1, 1, 15, 45)
end
it "retrieves the right events" do
query = FutureEvents.new
expect(query.call(23.years)).to include(event)
end
Have fun debugging!
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
Smell
class Event < ApplicationRecord
before_validation :set_datetimes_to_date
def set_datetimes_to_date
base_date = date.to_datetime
DATE_TIME_FIELDS.each do |time_attribute|
original = public_send(time_attribute)
if original
adjusted_time =
base_date.change hour: original.hour,
min: original.min
self.public_send("#{time_attribute}=", adjusted_time)
end
end
end
end
Why does the model clean up
Validations
validate :unique_email, if: :email_changed?
validate :owns_notification_email,
if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
gitlab/user
validate :unique_email, if: :email_changed?
validate :owns_notification_email,
if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
gitlab/user
Why the effort?
Practice open?
Practice open?
No overlap?
Practice open?
No overlap?
Right skills?
Practice open?
No overlap?
Right skills?
Patient can be contacted?
Practice open?
No overlap?
Right skills?
Patient can be contacted?
Associated models
Practice open?
No overlap?
Right skills?
Patient can be contacted?
Associated models
...
Expensive Test Setup
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
before_validation :set_commit_email, if: :commit_email_changed?
# in case validation is skipped
before_save :set_public_email, if: :public_email_changed?
# in case validation is skipped
before_save :set_commit_email, if: :commit_email_changed?
before_save :skip_reconfirmation!, if: #>(user) {
user.email_changed? #& user.read_only_attribute?(:email)
}
before_save :check_for_verified_email, if: #>(user) {
user.email_changed? #& !user.new_record?
}
gitlab/user
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
before_validation :set_commit_email, if: :commit_email_changed?
# in case validation is skipped
before_save :set_public_email, if: :public_email_changed?
# in case validation is skipped
before_save :set_commit_email, if: :commit_email_changed?
before_save :skip_reconfirmation!, if: #>(user) {
user.email_changed? #& user.read_only_attribute?(:email)
}
before_save :check_for_verified_email, if: #>(user) {
user.email_changed? #& !user.new_record?
}
gitlab/user
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
before_validation :set_commit_email, if: :commit_email_changed?
# in case validation is skipped
before_save :set_public_email, if: :public_email_changed?
# in case validation is skipped
before_save :set_commit_email, if: :commit_email_changed?
before_save :skip_reconfirmation!, if: #>(user) {
user.email_changed? #& user.read_only_attribute?(:email)
}
before_save :check_for_verified_email, if: #>(user) {
user.email_changed? #& !user.new_record?
}
Ways to change?
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
before_validation :set_commit_email, if: :commit_email_changed?
# in case validation is skipped
before_save :set_public_email, if: :public_email_changed?
# in case validation is skipped
before_save :set_commit_email, if: :commit_email_changed?
before_save :skip_reconfirmation!, if: #>(user) {
user.email_changed? #& user.read_only_attribute?(:email)
}
before_save :check_for_verified_email, if: #>(user) {
user.email_changed? #& !user.new_record?
}
gitlab/user
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
before_validation :set_commit_email, if: :commit_email_changed?
# in case validation is skipped
before_save :set_public_email, if: :public_email_changed?
# in case validation is skipped
before_save :set_commit_email, if: :commit_email_changed?
before_save :skip_reconfirmation!, if: #>(user) {
user.email_changed? #& user.read_only_attribute?(:email)
}
before_save :check_for_verified_email, if: #>(user) {
user.email_changed? #& !user.new_record?
}
gitlab/user
Why are we
doing this?
Affordance
Wants to be cuddled
Wants to be fed
class MySolution
def do_thing(argument)
end
end
OOP Affordance
Model
View
Controller
Rails Affordance
“Fat Models
Skinny Controllers”
1 or 2 use cases
stuck on every model
Controllers
Models
Registrations
User
Registrations
User
Users
Registrations
User
UsersUsers ##. ##. ##.
Controllers
Models
Service Objects
Registrations
User
UsersUsers ##. ##. ##.
##. ##. ##. ##. ##.
Registrations
User
UsersUsers ##. ##. ##.
##. ##. ##. ##. ##.
Business Logic Separated
Registrations
User
UsersUsers ##. ##. ##.
##. ##. ##. ##. ##.
Business Logic Separated
Validations and Callbacks still mixed
Registrations
User
UsersUsers ##. ##. ##.
##. ##. ##. ##. ##.
Business Logic Separated
Validations and Callbacks still mixed
Run all the time by default
Registrations
User
UsersUsers ##. ##. ##.
##. ##. ##. ##. ##.
SignUp ShowIndex ##. ##. ##.
Registrations
User
##.
SignUpView
Controller
Service
Model
class User < ApplicationRecord
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
A User Model
class User < ApplicationRecord
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
Sign Up / Edit Only
class User < ApplicationRecord
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
View Related
class User < ApplicationRecord
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
WHWWHHYYY???
Registrations
User
##.
SignUpView
Controller
Service
Model
Knowledge?
Registrations
User
##.
SignUpView
Controller
Service
Model
Why Solve it here?
Registrations
User
##.
SignUpView
Controller
Service
Model
Opt Out
Registrations
User
##.
SignUpView
Controller
Service
Model
Could solve here
Registrations
User
##.
SignUpView
Controller
Service
Model
Or here?
Tobi complaining
about validations
and callbacks
You’ve seen:
Do You Need That Validation?
Let Me Call You Back About It
Tobias Pfeiffer
@PragTob
pragtob.info
Specifics clutter Model
Specifics clutter Model
Hard to get overiew
Specifics clutter Model
Hard to get overiew
Run all the time
class User < ApplicationRecord
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
ActiveRecord Original
What does Rails offer?
module UserRegistration
extend ActiveSupport#:Concern
included do
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
end
end
Concerns
module Copyable
def copy_to(destination)
Notification.suppress do
# Copy logic that creates new
# comments that we do not want
# triggering notifications.
end
end
end
Suppress
class Person < ApplicationRecord
validates :email,
uniqueness: true,
on: :account_setup
validates :age,
numericality: true,
on: :account_setup
end
Custom Contexts
What’s out there?
Form Objects
Form ObjectsForm ObjectsForm Objects
class Registration
include ActiveModel#:Model
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :email, :password
def save
if valid?
user = BaseUser.new(email: email, password_digest: hash_password)
user.save!
send_welcome_email
true
else
false
end
end
end
Form ObjectsForm ObjectsPlain ActiveModel
class Registration
include ActiveModel#:Model
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :email, :password
def save
if valid?
user = BaseUser.new(email: email, password_digest: hash_password)
user.save!
send_welcome_email
true
else
false
end
end
end
Form ObjectsForm ObjectsValidations
class Registration
include ActiveModel#:Model
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :email, :password
###.
end
Form ObjectsForm ObjectsAttributes
class Registration
include ActiveModel#:Model
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :email, :password
###.
end
Form ObjectsForm ObjectsMap to ActiveRecord
class Registration
###.
def save
if valid?
user = BaseUser.new(
email: email,
password_digest: hash_password
)
user.save!
send_welcome_email
true
else
false
end
end
Form ObjectsForm ObjectsInterface
class Registration
###.
def save
if valid?
user = BaseUser.new(
email: email,
password_digest: hash_password
)
user.save!
send_welcome_email
true
else
false
end
end
Form ObjectsForm ObjectsCallbacks
class Registration
###.
def save
if valid?
user = BaseUser.new(
email: email,
password_digest: hash_password
)
user.save!
send_welcome_email
true
else
false
end
end
def create
@user = Registration.new(registration_params)
if @user.save
# ##.
else
# ##.
end
end
Same Interface
Inheritance!
Inheritance!
class User#:AsSignUp < ActiveType#:Record[User]
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
Inheritance!
class User#:AsSignUp < ActiveType#:Record[User]
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
ActiveType
class User#:AsSignUp < ActiveType#:Record[User]
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
class User < ApplicationRecord
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
Original
Almost the same!
class User#:AsSignUp < ActiveType#:Record[User]
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
Handle STI, Routes etc.
class User#:AsSignUp < ActiveType#:Record[User]
validates :email,
presence: true,
confirmation: true
validates :password,
confirmation: true,
length: { minimum: 8 }
validates :terms, acceptance: true
attr_accessor :password
before_save :hash_password
after_commit :send_welcome_email, on: :create
###.
end
Changesets
Changesets
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
Elixir
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
Pipe
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
Context
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
“strong parameters”
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
Validations
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
callback
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
Mixing concerns
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
before_validation :set_commit_email, if: :commit_email_changed?
# in case validation is skipped
before_save :set_public_email, if: :public_email_changed?
# in case validation is skipped
before_save :set_commit_email, if: :commit_email_changed?
before_save :skip_reconfirmation!, if: #>(user) {
user.email_changed? #& user.read_only_attribute?(:email)
}
before_save :check_for_verified_email, if: #>(user) {
user.email_changed? #& !user.new_record?
}
Remember this?
Combinable
defmodule ValidationShowcase.Accounts.User do
# ...
def registration_changeset(user, attrs) do
user
|> base_changeset(attrs)
|> cast(attrs, [:email, :password, :terms_of_service])
|> validate_required([:email, :password])
|> validate_confirmation(:email)
|> validate_confirmation(:password)
|> validate_length(:password, min: 8)
|> validate_acceptance(:terms_of_service)
|> hash_password()
end
end
defmodule ValidationShowcase.Accounts do
def create_user(attrs  %{}) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
|> send_welcome_email()
end
end
Context
defmodule ValidationShowcase.Accounts do
def create_user(attrs  %{}) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
|> send_welcome_email()
end
end
Changeset
defmodule ValidationShowcase.Accounts do
def create_user(attrs  %{}) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
|> send_welcome_email()
end
end
“after_commit”
defmodule ValidationShowcaseWeb.UserController do
def create(conn, %{"user" => user_params}) do
case Accounts.create_user(user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User created successfully.")
|> redirect(to: Routes.user_path(conn, :show, user))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
end
Controller
Form ObjectsForm ObjectsSeparate Operations and Validators
trailblazer
class TbRegistrationsController < ApplicationController
def create
result = Registration#:Create.(params: params)
if result.success?
redirect_to "/users/", notice: 'User was created.'
else
@user = result["contract.default"]
render "users/new"
end
end
end
class TbRegistrationsController < ApplicationController
def create
result = Registration#:Create.(params: params)
if result.success?
redirect_to "/users/", notice: 'User was created.'
else
@user = result["contract.default"]
render "users/new"
end
end
end
Operation
class Registration#:Create < Trailblazer#:Operation
step Model(BaseUser, :new)
step Contract#:Build(
constant: Registration#:Contract#:Create
)
step Contract#:Validate(key: :tb_registration)
step :hash_password
step Contract#:Persist()
step :send_welcome_email
end
class Registration#:Create < Trailblazer#:Operation
step Model(BaseUser, :new)
step Contract#:Build(
constant: Registration#:Contract#:Create
)
step Contract#:Validate(key: :tb_registration)
step :hash_password
step Contract#:Persist()
step :send_welcome_email
end
Setup Model
class Registration#:Create < Trailblazer#:Operation
step Model(BaseUser, :new)
step Contract#:Build(
constant: Registration#:Contract#:Create
)
step Contract#:Validate(key: :tb_registration)
step :hash_password
step Contract#:Persist()
step :send_welcome_email
end
Setup Form Object
module Registration#:Contract
class Create < Reform#:Form
include Dry
include Reform#:Form#:ActiveModel
feature Coercion
model :tb_registration
property :email
property :email_confirmation, virtual: true
property :password, virtual: true
property :password_confirmation, virtual: true
property :terms, virtual: true, type: Types#:Params#:Bool
validation do
required(:email).filled.confirmation
required(:password).value(min_size?: 8).confirmation
required(:terms).value(:true?)
end
end
end
module Registration#:Contract
class Create < Reform#:Form
include Dry
include Reform#:Form#:ActiveModel
feature Coercion
model :tb_registration
property :email
property :email_confirmation, virtual: true
property :password, virtual: true
property :password_confirmation, virtual: true
property :terms, virtual: true, type: Types#:Params#:Bool
validation do
required(:email).filled.confirmation
required(:password).value(min_size?: 8).confirmation
required(:terms).value(:true?)
end
end
end
module Registration#:Contract
class Create < Reform#:Form
property :email
property :email_confirmation, virtual: true
property :password, virtual: true
property :password_confirmation, virtual: true
property :terms, virtual: true,
type: Types#:Params#:Bool
validation do
required(:email).filled.confirmation
required(:password).value(min_size?: 8).confirmation
required(:terms).value(:true?)
end
end
end
Attributes
module Registration#:Contract
class Create < Reform#:Form
property :email
property :email_confirmation, virtual: true
property :password, virtual: true
property :password_confirmation, virtual: true
property :terms, virtual: true,
type: Types#:Params#:Bool
validation do
required(:email).filled.confirmation
required(:password).value(min_size?: 8).confirmation
required(:terms).value(:true?)
end
end
end
dry-validation
module Registration#:Contract
class Create < Reform#:Form
property :email
property :email_confirmation, virtual: true
property :password, virtual: true
property :password_confirmation, virtual: true
property :terms, virtual: true,
type: Types#:Params#:Bool
validation do
required(:email).filled.confirmation
required(:password).value(min_size#: 8).confirmation
required(:terms).value(:true?)
end
end
end
class Registration#:Create < Trailblazer#:Operation
step Model(BaseUser, :new)
step Contract#:Build(
constant: Registration#:Contract#:Create
)
step Contract#:Validate(key: :tb_registration)
step :hash_password
step Contract#:Persist()
step :send_welcome_email
end
validate
class Registration#:Create < Trailblazer#:Operation
step Model(BaseUser, :new)
step Contract#:Build(
constant: Registration#:Contract#:Create
)
step Contract#:Validate(key: :tb_registration)
step :hash_password
step Contract#:Persist()
step :send_welcome_email
end
Callback
class Registration#:Create < Trailblazer#:Operation
step Model(BaseUser, :new)
step Contract#:Build(
constant: Registration#:Contract#:Create
)
step Contract#:Validate(key: :tb_registration)
step :hash_password
step Contract#:Persist()
step :send_welcome_email
end
Persist
class Registration#:Create < Trailblazer#:Operation
step Model(BaseUser, :new)
step Contract#:Build(
constant: Registration#:Contract#:Create
)
step Contract#:Validate(key: :tb_registration)
step :hash_password
step Contract#:Persist()
step :send_welcome_email
end
Callback
“Models are persistence-only and
solely define associations and
scopes. No business code is to be
found here. No validations, no
callbacks.”
trailblazer
The architecture eases keeping the
business logic (entities) separated
from details such as persistence or
validations.
hanami/model
Takeaway
I don’t hate Rails
I don’t hate Rails
Future in Rails?
I don’t hate Rails
Affordances
Future in Rails?
I don’t hate Rails
Alternatives
Affordances
Future in Rails?
Form Objects
Inheritance!
Changesets
Form ObjectsForm ObjectsSeparate Operations and Validators
I don’t hate Rails
Careful with validations and callbacks
Alternatives
Affordances
Future in Rails?
Thank you!

Contenu connexe

Similaire à Do You Need That Validation? Let Me Call You Back About It

Don't Settle for Poor Names (Or Poor Design)
Don't Settle for Poor Names (Or Poor Design)Don't Settle for Poor Names (Or Poor Design)
Don't Settle for Poor Names (Or Poor Design)Alistair McKinnell
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web servicesMichelangelo van Dam
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelpauldix
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelpauldix
 
Very basic functional design patterns
Very basic functional design patternsVery basic functional design patterns
Very basic functional design patternsTomasz Kowal
 
Simplify Your Rails Controllers With a Vengeance
Simplify Your Rails Controllers With a VengeanceSimplify Your Rails Controllers With a Vengeance
Simplify Your Rails Controllers With a Vengeancebrianauton
 
Akka persistence webinar
Akka persistence webinarAkka persistence webinar
Akka persistence webinarpatriknw
 
Testing C# and ASP.net using Ruby
Testing C# and ASP.net using RubyTesting C# and ASP.net using Ruby
Testing C# and ASP.net using RubyBen Hall
 
Let it crash - fault tolerance in Elixir/OTP
Let it crash - fault tolerance in Elixir/OTPLet it crash - fault tolerance in Elixir/OTP
Let it crash - fault tolerance in Elixir/OTPMaciej Kaszubowski
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the FinishYehuda Katz
 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy Codescidept
 
Hexagonal architecture & Elixir
Hexagonal architecture & ElixirHexagonal architecture & Elixir
Hexagonal architecture & ElixirNicolas Carlo
 
Introduce cucumber
Introduce cucumberIntroduce cucumber
Introduce cucumberBachue Zhou
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015Fernando Daciuk
 
AngularJS, More Than Directives !
AngularJS, More Than Directives !AngularJS, More Than Directives !
AngularJS, More Than Directives !Gaurav Behere
 
Two Trains and Other Refactoring Analogies
Two Trains and Other Refactoring AnalogiesTwo Trains and Other Refactoring Analogies
Two Trains and Other Refactoring AnalogiesKevin London
 

Similaire à Do You Need That Validation? Let Me Call You Back About It (20)

Don't Settle for Poor Names (Or Poor Design)
Don't Settle for Poor Names (Or Poor Design)Don't Settle for Poor Names (Or Poor Design)
Don't Settle for Poor Names (Or Poor Design)
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
 
Very basic functional design patterns
Very basic functional design patternsVery basic functional design patterns
Very basic functional design patterns
 
Simplify Your Rails Controllers With a Vengeance
Simplify Your Rails Controllers With a VengeanceSimplify Your Rails Controllers With a Vengeance
Simplify Your Rails Controllers With a Vengeance
 
Akka persistence webinar
Akka persistence webinarAkka persistence webinar
Akka persistence webinar
 
JavaScript Refactoring
JavaScript RefactoringJavaScript Refactoring
JavaScript Refactoring
 
Testing C# and ASP.net using Ruby
Testing C# and ASP.net using RubyTesting C# and ASP.net using Ruby
Testing C# and ASP.net using Ruby
 
Let it crash - fault tolerance in Elixir/OTP
Let it crash - fault tolerance in Elixir/OTPLet it crash - fault tolerance in Elixir/OTP
Let it crash - fault tolerance in Elixir/OTP
 
Jasmine frontinrio
Jasmine frontinrioJasmine frontinrio
Jasmine frontinrio
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy Code
 
Hexagonal architecture & Elixir
Hexagonal architecture & ElixirHexagonal architecture & Elixir
Hexagonal architecture & Elixir
 
Well
WellWell
Well
 
Introduce cucumber
Introduce cucumberIntroduce cucumber
Introduce cucumber
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
 
AngularJS, More Than Directives !
AngularJS, More Than Directives !AngularJS, More Than Directives !
AngularJS, More Than Directives !
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
Two Trains and Other Refactoring Analogies
Two Trains and Other Refactoring AnalogiesTwo Trains and Other Refactoring Analogies
Two Trains and Other Refactoring Analogies
 

Plus de Tobias Pfeiffer

Metaphors are everywhere: Ideas to Improve Software Development
 Metaphors are everywhere: Ideas to Improve Software Development  Metaphors are everywhere: Ideas to Improve Software Development
Metaphors are everywhere: Ideas to Improve Software Development Tobias Pfeiffer
 
Elixir & Phoenix – Fast, Concurrent and Explicit
Elixir & Phoenix – Fast, Concurrent and ExplicitElixir & Phoenix – Fast, Concurrent and Explicit
Elixir & Phoenix – Fast, Concurrent and ExplicitTobias Pfeiffer
 
Functioning Among Humans
Functioning Among HumansFunctioning Among Humans
Functioning Among HumansTobias Pfeiffer
 
Functioning Among Humans
Functioning Among HumansFunctioning Among Humans
Functioning Among HumansTobias Pfeiffer
 
Elixir, your Monolith and You
Elixir, your Monolith and YouElixir, your Monolith and You
Elixir, your Monolith and YouTobias Pfeiffer
 
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)Tobias Pfeiffer
 
It's About the Humans, Stupid (Lightning)
It's About the Humans, Stupid (Lightning)It's About the Humans, Stupid (Lightning)
It's About the Humans, Stupid (Lightning)Tobias Pfeiffer
 
Stop Guessing and Start Measuring - Benchmarking Practice (Poly Version)
 Stop Guessing and Start Measuring - Benchmarking Practice (Poly Version) Stop Guessing and Start Measuring - Benchmarking Practice (Poly Version)
Stop Guessing and Start Measuring - Benchmarking Practice (Poly Version)Tobias Pfeiffer
 
Code, Comments, Concepts, Comprehension – Conclusion?
Code, Comments, Concepts, Comprehension – Conclusion?Code, Comments, Concepts, Comprehension – Conclusion?
Code, Comments, Concepts, Comprehension – Conclusion?Tobias Pfeiffer
 
How fast is it really? Benchmarking in Practice (Ruby Version)
How fast is it really? Benchmarking in Practice (Ruby Version)How fast is it really? Benchmarking in Practice (Ruby Version)
How fast is it really? Benchmarking in Practice (Ruby Version)Tobias Pfeiffer
 
How fast ist it really? Benchmarking in practice
How fast ist it really? Benchmarking in practiceHow fast ist it really? Benchmarking in practice
How fast ist it really? Benchmarking in practiceTobias Pfeiffer
 
Introducing Elixir the easy way
Introducing Elixir the easy wayIntroducing Elixir the easy way
Introducing Elixir the easy wayTobias Pfeiffer
 
Elixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitElixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitTobias Pfeiffer
 
What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?Tobias Pfeiffer
 
Elixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitElixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitTobias Pfeiffer
 
What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?Tobias Pfeiffer
 

Plus de Tobias Pfeiffer (20)

Going Staff
Going StaffGoing Staff
Going Staff
 
Stories in Open SOurce
Stories in Open SOurceStories in Open SOurce
Stories in Open SOurce
 
Metaphors are everywhere: Ideas to Improve Software Development
 Metaphors are everywhere: Ideas to Improve Software Development  Metaphors are everywhere: Ideas to Improve Software Development
Metaphors are everywhere: Ideas to Improve Software Development
 
Stories in Open Source
Stories in Open SourceStories in Open Source
Stories in Open Source
 
Elixir & Phoenix – Fast, Concurrent and Explicit
Elixir & Phoenix – Fast, Concurrent and ExplicitElixir & Phoenix – Fast, Concurrent and Explicit
Elixir & Phoenix – Fast, Concurrent and Explicit
 
Functioning Among Humans
Functioning Among HumansFunctioning Among Humans
Functioning Among Humans
 
Functioning Among Humans
Functioning Among HumansFunctioning Among Humans
Functioning Among Humans
 
Elixir, your Monolith and You
Elixir, your Monolith and YouElixir, your Monolith and You
Elixir, your Monolith and You
 
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
 
Where do Rubyists go?
 Where do Rubyists go?  Where do Rubyists go?
Where do Rubyists go?
 
It's About the Humans, Stupid (Lightning)
It's About the Humans, Stupid (Lightning)It's About the Humans, Stupid (Lightning)
It's About the Humans, Stupid (Lightning)
 
Stop Guessing and Start Measuring - Benchmarking Practice (Poly Version)
 Stop Guessing and Start Measuring - Benchmarking Practice (Poly Version) Stop Guessing and Start Measuring - Benchmarking Practice (Poly Version)
Stop Guessing and Start Measuring - Benchmarking Practice (Poly Version)
 
Code, Comments, Concepts, Comprehension – Conclusion?
Code, Comments, Concepts, Comprehension – Conclusion?Code, Comments, Concepts, Comprehension – Conclusion?
Code, Comments, Concepts, Comprehension – Conclusion?
 
How fast is it really? Benchmarking in Practice (Ruby Version)
How fast is it really? Benchmarking in Practice (Ruby Version)How fast is it really? Benchmarking in Practice (Ruby Version)
How fast is it really? Benchmarking in Practice (Ruby Version)
 
How fast ist it really? Benchmarking in practice
How fast ist it really? Benchmarking in practiceHow fast ist it really? Benchmarking in practice
How fast ist it really? Benchmarking in practice
 
Introducing Elixir the easy way
Introducing Elixir the easy wayIntroducing Elixir the easy way
Introducing Elixir the easy way
 
Elixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitElixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicit
 
What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?
 
Elixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitElixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicit
 
What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?
 

Dernier

Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfOverkill Security
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobeapidays
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...apidays
 
AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024The Digital Insurer
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...apidays
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Zilliz
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoffsammart93
 
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbuapidays
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWERMadyBayot
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingEdi Saputra
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century educationjfdjdjcjdnsjd
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxRustici Software
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProduct Anonymous
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsNanddeep Nachan
 

Dernier (20)

Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 

Do You Need That Validation? Let Me Call You Back About It

  • 6. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end
  • 7. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end
  • 8. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end
  • 9. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end Datetime fields
  • 10. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end
  • 11. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end
  • 12. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end
  • 13. event = Event.new( name: "Ruby On Ice", location: "Tegernsee", date: "24.02.2019", crew_arrives_at: "6:45", performers_arrive_at: "9:30", open_at: "9:30", starts_at: "10:00", ends_at: "16:00" ) event.valid?
  • 14. #<Event: name: "Ruby On Ice", location: "Tegernsee", date: Sun, 24 Feb 2019, crew_arrives_at: Sun, 24 Feb 2019 6:45:00, performers_arrive_at: Sun, 24 Feb 2019 9:00:00, open_at: Sun, 24 Feb 2019 9:00:00, starts_at: Sun, 24 Feb 2019 9:30:00, ends_at: Sun, 24 Feb 2019 20:00:00> It works!
  • 15.
  • 16. let(:event) do build :event, ends_at: Time.zone.local(2042, 1, 1, 15, 45) end
  • 17. let(:event) do build :event, ends_at: Time.zone.local(2042, 1, 1, 15, 45) end
  • 18. let(:event) do build :event, ends_at: Time.zone.local(2042, 1, 1, 15, 45) end it "works" do p event.ends_at # Wed, 01 Jan 2042 15:45:00 event.save! p event.ends_at # Sun, 24 Feb 2019 15:45:00 end Have fun debugging!
  • 19.
  • 20. let(:event) do create :event, ends_at: Time.zone.local(2042, 1, 1, 15, 45) end it "retrieves the right events" do query = FutureEvents.new expect(query.call(23.years)).to include(event) end Have fun debugging!
  • 21. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end
  • 22. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end Smell
  • 23. class Event < ApplicationRecord before_validation :set_datetimes_to_date def set_datetimes_to_date base_date = date.to_datetime DATE_TIME_FIELDS.each do |time_attribute| original = public_send(time_attribute) if original adjusted_time = base_date.change hour: original.hour, min: original.min self.public_send("#{time_attribute}=", adjusted_time) end end end end Why does the model clean up
  • 25. validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? validate :owns_public_email, if: :public_email_changed? validate :owns_commit_email, if: :commit_email_changed? gitlab/user
  • 26. validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? validate :owns_public_email, if: :public_email_changed? validate :owns_commit_email, if: :commit_email_changed? gitlab/user
  • 31. Practice open? No overlap? Right skills? Patient can be contacted?
  • 32. Practice open? No overlap? Right skills? Patient can be contacted? Associated models
  • 33. Practice open? No overlap? Right skills? Patient can be contacted? Associated models ...
  • 35. validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? validate :owns_public_email, if: :public_email_changed? validate :owns_commit_email, if: :commit_email_changed? before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? before_validation :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :set_public_email, if: :public_email_changed? # in case validation is skipped before_save :set_commit_email, if: :commit_email_changed? before_save :skip_reconfirmation!, if: #>(user) { user.email_changed? #& user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: #>(user) { user.email_changed? #& !user.new_record? } gitlab/user
  • 36. validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? validate :owns_public_email, if: :public_email_changed? validate :owns_commit_email, if: :commit_email_changed? before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? before_validation :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :set_public_email, if: :public_email_changed? # in case validation is skipped before_save :set_commit_email, if: :commit_email_changed? before_save :skip_reconfirmation!, if: #>(user) { user.email_changed? #& user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: #>(user) { user.email_changed? #& !user.new_record? } gitlab/user
  • 37. validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? validate :owns_public_email, if: :public_email_changed? validate :owns_commit_email, if: :commit_email_changed? before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? before_validation :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :set_public_email, if: :public_email_changed? # in case validation is skipped before_save :set_commit_email, if: :commit_email_changed? before_save :skip_reconfirmation!, if: #>(user) { user.email_changed? #& user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: #>(user) { user.email_changed? #& !user.new_record? } Ways to change?
  • 38. validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? validate :owns_public_email, if: :public_email_changed? validate :owns_commit_email, if: :commit_email_changed? before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? before_validation :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :set_public_email, if: :public_email_changed? # in case validation is skipped before_save :set_commit_email, if: :commit_email_changed? before_save :skip_reconfirmation!, if: #>(user) { user.email_changed? #& user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: #>(user) { user.email_changed? #& !user.new_record? } gitlab/user
  • 39. validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? validate :owns_public_email, if: :public_email_changed? validate :owns_commit_email, if: :commit_email_changed? before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? before_validation :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :set_public_email, if: :public_email_changed? # in case validation is skipped before_save :set_commit_email, if: :commit_email_changed? before_save :skip_reconfirmation!, if: #>(user) { user.email_changed? #& user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: #>(user) { user.email_changed? #& !user.new_record? } gitlab/user
  • 42. Wants to be cuddled
  • 43. Wants to be fed
  • 47. 1 or 2 use cases stuck on every model
  • 53. Registrations User UsersUsers ##. ##. ##. ##. ##. ##. ##. ##.
  • 54. Registrations User UsersUsers ##. ##. ##. ##. ##. ##. ##. ##. Business Logic Separated
  • 55. Registrations User UsersUsers ##. ##. ##. ##. ##. ##. ##. ##. Business Logic Separated Validations and Callbacks still mixed
  • 56. Registrations User UsersUsers ##. ##. ##. ##. ##. ##. ##. ##. Business Logic Separated Validations and Callbacks still mixed Run all the time by default
  • 57. Registrations User UsersUsers ##. ##. ##. ##. ##. ##. ##. ##. SignUp ShowIndex ##. ##. ##.
  • 59. class User < ApplicationRecord validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end A User Model
  • 60. class User < ApplicationRecord validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end Sign Up / Edit Only
  • 61. class User < ApplicationRecord validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end View Related
  • 62. class User < ApplicationRecord validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end WHWWHHYYY???
  • 66.
  • 67.
  • 70.
  • 71. Tobi complaining about validations and callbacks You’ve seen:
  • 72. Do You Need That Validation? Let Me Call You Back About It Tobias Pfeiffer @PragTob pragtob.info
  • 73.
  • 76. Specifics clutter Model Hard to get overiew Run all the time
  • 77. class User < ApplicationRecord validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end ActiveRecord Original
  • 78. What does Rails offer?
  • 79. module UserRegistration extend ActiveSupport#:Concern included do validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create end end Concerns
  • 80. module Copyable def copy_to(destination) Notification.suppress do # Copy logic that creates new # comments that we do not want # triggering notifications. end end end Suppress
  • 81. class Person < ApplicationRecord validates :email, uniqueness: true, on: :account_setup validates :age, numericality: true, on: :account_setup end Custom Contexts
  • 84. Form ObjectsForm ObjectsForm Objects class Registration include ActiveModel#:Model validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :email, :password def save if valid? user = BaseUser.new(email: email, password_digest: hash_password) user.save! send_welcome_email true else false end end end
  • 85. Form ObjectsForm ObjectsPlain ActiveModel class Registration include ActiveModel#:Model validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :email, :password def save if valid? user = BaseUser.new(email: email, password_digest: hash_password) user.save! send_welcome_email true else false end end end
  • 86. Form ObjectsForm ObjectsValidations class Registration include ActiveModel#:Model validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :email, :password ###. end
  • 87. Form ObjectsForm ObjectsAttributes class Registration include ActiveModel#:Model validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :email, :password ###. end
  • 88. Form ObjectsForm ObjectsMap to ActiveRecord class Registration ###. def save if valid? user = BaseUser.new( email: email, password_digest: hash_password ) user.save! send_welcome_email true else false end end
  • 89. Form ObjectsForm ObjectsInterface class Registration ###. def save if valid? user = BaseUser.new( email: email, password_digest: hash_password ) user.save! send_welcome_email true else false end end
  • 90. Form ObjectsForm ObjectsCallbacks class Registration ###. def save if valid? user = BaseUser.new( email: email, password_digest: hash_password ) user.save! send_welcome_email true else false end end
  • 91. def create @user = Registration.new(registration_params) if @user.save # ##. else # ##. end end Same Interface
  • 93. Inheritance! class User#:AsSignUp < ActiveType#:Record[User] validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end
  • 94. Inheritance! class User#:AsSignUp < ActiveType#:Record[User] validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end
  • 95. ActiveType class User#:AsSignUp < ActiveType#:Record[User] validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end
  • 96. class User < ApplicationRecord validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end Original
  • 97. Almost the same! class User#:AsSignUp < ActiveType#:Record[User] validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end
  • 98. Handle STI, Routes etc. class User#:AsSignUp < ActiveType#:Record[User] validates :email, presence: true, confirmation: true validates :password, confirmation: true, length: { minimum: 8 } validates :terms, acceptance: true attr_accessor :password before_save :hash_password after_commit :send_welcome_email, on: :create ###. end
  • 100. Changesets defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 101. Elixir defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 102. Pipe defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 103. Context defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 104. “strong parameters” defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 105. Validations defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 106. callback defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 107. Mixing concerns defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 108. validate :unique_email, if: :email_changed? validate :owns_notification_email, if: :notification_email_changed? validate :owns_public_email, if: :public_email_changed? validate :owns_commit_email, if: :commit_email_changed? before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? before_validation :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :set_public_email, if: :public_email_changed? # in case validation is skipped before_save :set_commit_email, if: :commit_email_changed? before_save :skip_reconfirmation!, if: #>(user) { user.email_changed? #& user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: #>(user) { user.email_changed? #& !user.new_record? } Remember this?
  • 109. Combinable defmodule ValidationShowcase.Accounts.User do # ... def registration_changeset(user, attrs) do user |> base_changeset(attrs) |> cast(attrs, [:email, :password, :terms_of_service]) |> validate_required([:email, :password]) |> validate_confirmation(:email) |> validate_confirmation(:password) |> validate_length(:password, min: 8) |> validate_acceptance(:terms_of_service) |> hash_password() end end
  • 110. defmodule ValidationShowcase.Accounts do def create_user(attrs %{}) do %User{} |> User.registration_changeset(attrs) |> Repo.insert() |> send_welcome_email() end end Context
  • 111. defmodule ValidationShowcase.Accounts do def create_user(attrs %{}) do %User{} |> User.registration_changeset(attrs) |> Repo.insert() |> send_welcome_email() end end Changeset
  • 112. defmodule ValidationShowcase.Accounts do def create_user(attrs %{}) do %User{} |> User.registration_changeset(attrs) |> Repo.insert() |> send_welcome_email() end end “after_commit”
  • 113. defmodule ValidationShowcaseWeb.UserController do def create(conn, %{"user" => user_params}) do case Accounts.create_user(user_params) do {:ok, user} -> conn |> put_flash(:info, "User created successfully.") |> redirect(to: Routes.user_path(conn, :show, user)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) end end end Controller
  • 114. Form ObjectsForm ObjectsSeparate Operations and Validators
  • 116. class TbRegistrationsController < ApplicationController def create result = Registration#:Create.(params: params) if result.success? redirect_to "/users/", notice: 'User was created.' else @user = result["contract.default"] render "users/new" end end end
  • 117. class TbRegistrationsController < ApplicationController def create result = Registration#:Create.(params: params) if result.success? redirect_to "/users/", notice: 'User was created.' else @user = result["contract.default"] render "users/new" end end end Operation
  • 118. class Registration#:Create < Trailblazer#:Operation step Model(BaseUser, :new) step Contract#:Build( constant: Registration#:Contract#:Create ) step Contract#:Validate(key: :tb_registration) step :hash_password step Contract#:Persist() step :send_welcome_email end
  • 119. class Registration#:Create < Trailblazer#:Operation step Model(BaseUser, :new) step Contract#:Build( constant: Registration#:Contract#:Create ) step Contract#:Validate(key: :tb_registration) step :hash_password step Contract#:Persist() step :send_welcome_email end Setup Model
  • 120. class Registration#:Create < Trailblazer#:Operation step Model(BaseUser, :new) step Contract#:Build( constant: Registration#:Contract#:Create ) step Contract#:Validate(key: :tb_registration) step :hash_password step Contract#:Persist() step :send_welcome_email end Setup Form Object
  • 121. module Registration#:Contract class Create < Reform#:Form include Dry include Reform#:Form#:ActiveModel feature Coercion model :tb_registration property :email property :email_confirmation, virtual: true property :password, virtual: true property :password_confirmation, virtual: true property :terms, virtual: true, type: Types#:Params#:Bool validation do required(:email).filled.confirmation required(:password).value(min_size?: 8).confirmation required(:terms).value(:true?) end end end
  • 122. module Registration#:Contract class Create < Reform#:Form include Dry include Reform#:Form#:ActiveModel feature Coercion model :tb_registration property :email property :email_confirmation, virtual: true property :password, virtual: true property :password_confirmation, virtual: true property :terms, virtual: true, type: Types#:Params#:Bool validation do required(:email).filled.confirmation required(:password).value(min_size?: 8).confirmation required(:terms).value(:true?) end end end
  • 123. module Registration#:Contract class Create < Reform#:Form property :email property :email_confirmation, virtual: true property :password, virtual: true property :password_confirmation, virtual: true property :terms, virtual: true, type: Types#:Params#:Bool validation do required(:email).filled.confirmation required(:password).value(min_size?: 8).confirmation required(:terms).value(:true?) end end end
  • 124. Attributes module Registration#:Contract class Create < Reform#:Form property :email property :email_confirmation, virtual: true property :password, virtual: true property :password_confirmation, virtual: true property :terms, virtual: true, type: Types#:Params#:Bool validation do required(:email).filled.confirmation required(:password).value(min_size?: 8).confirmation required(:terms).value(:true?) end end end
  • 125. dry-validation module Registration#:Contract class Create < Reform#:Form property :email property :email_confirmation, virtual: true property :password, virtual: true property :password_confirmation, virtual: true property :terms, virtual: true, type: Types#:Params#:Bool validation do required(:email).filled.confirmation required(:password).value(min_size#: 8).confirmation required(:terms).value(:true?) end end end
  • 126. class Registration#:Create < Trailblazer#:Operation step Model(BaseUser, :new) step Contract#:Build( constant: Registration#:Contract#:Create ) step Contract#:Validate(key: :tb_registration) step :hash_password step Contract#:Persist() step :send_welcome_email end validate
  • 127. class Registration#:Create < Trailblazer#:Operation step Model(BaseUser, :new) step Contract#:Build( constant: Registration#:Contract#:Create ) step Contract#:Validate(key: :tb_registration) step :hash_password step Contract#:Persist() step :send_welcome_email end Callback
  • 128. class Registration#:Create < Trailblazer#:Operation step Model(BaseUser, :new) step Contract#:Build( constant: Registration#:Contract#:Create ) step Contract#:Validate(key: :tb_registration) step :hash_password step Contract#:Persist() step :send_welcome_email end Persist
  • 129. class Registration#:Create < Trailblazer#:Operation step Model(BaseUser, :new) step Contract#:Build( constant: Registration#:Contract#:Create ) step Contract#:Validate(key: :tb_registration) step :hash_password step Contract#:Persist() step :send_welcome_email end Callback
  • 130. “Models are persistence-only and solely define associations and scopes. No business code is to be found here. No validations, no callbacks.” trailblazer
  • 131. The architecture eases keeping the business logic (entities) separated from details such as persistence or validations. hanami/model
  • 133. I don’t hate Rails
  • 134. I don’t hate Rails Future in Rails?
  • 135. I don’t hate Rails Affordances Future in Rails?
  • 136. I don’t hate Rails Alternatives Affordances Future in Rails?
  • 140. Form ObjectsForm ObjectsSeparate Operations and Validators
  • 141. I don’t hate Rails Careful with validations and callbacks Alternatives Affordances Future in Rails?