SlideShare a Scribd company logo
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!
Do You Need That Validation? Let Me Call You Back About It
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!
Do You Need That Validation? Let Me Call You Back About It
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
Do You Need That Validation? Let Me Call You Back About It
Do You Need That Validation? Let Me Call You Back About It
Registrations
User
##.
SignUpView
Controller
Service
Model
Could solve here
Registrations
User
##.
SignUpView
Controller
Service
Model
Or here?
Do You Need That Validation? Let Me Call You Back About It
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
Do You Need That Validation? Let Me Call You Back About It
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!

More Related Content

Similar to 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 services
Michelangelo van Dam
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
pauldix
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
pauldix
 
Very basic functional design patterns
Very basic functional design patternsVery basic functional design patterns
Very basic functional design patterns
Tomasz 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 Vengeance
brianauton
 
Akka persistence webinar
Akka persistence webinarAkka persistence webinar
Akka persistence webinar
patriknw
 
JavaScript Refactoring
JavaScript RefactoringJavaScript Refactoring
JavaScript Refactoring
Krzysztof Szafranek
 
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
Ben 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/OTP
Maciej Kaszubowski
 
Jasmine frontinrio
Jasmine frontinrioJasmine frontinrio
Jasmine frontinrio
Andre Fonseca
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
Yehuda Katz
 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy Code
scidept
 
Hexagonal architecture & Elixir
Hexagonal architecture & ElixirHexagonal architecture & Elixir
Hexagonal architecture & Elixir
Nicolas Carlo
 
Well
WellWell
Well
breccan
 
Introduce cucumber
Introduce cucumberIntroduce cucumber
Introduce cucumber
Bachue 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 2015
Fernando Daciuk
 
AngularJS, More Than Directives !
AngularJS, More Than Directives !AngularJS, More Than Directives !
AngularJS, More Than Directives !
Gaurav Behere
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
Tagged Social
 
Two Trains and Other Refactoring Analogies
Two Trains and Other Refactoring AnalogiesTwo Trains and Other Refactoring Analogies
Two Trains and Other Refactoring Analogies
Kevin London
 

Similar to 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
 

More from Tobias Pfeiffer

Going Staff
Going StaffGoing Staff
Going Staff
Tobias Pfeiffer
 
Stories in Open SOurce
Stories in Open SOurceStories in Open SOurce
Stories in Open SOurce
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
 
Stories in Open Source
Stories in Open SourceStories in Open Source
Stories in Open Source
Tobias Pfeiffer
 
Elixir & Phoenix – Fast, Concurrent and Explicit
Elixir & Phoenix – Fast, Concurrent and ExplicitElixir & Phoenix – Fast, Concurrent and Explicit
Elixir & Phoenix – Fast, Concurrent and Explicit
Tobias Pfeiffer
 
Functioning Among Humans
Functioning Among HumansFunctioning Among Humans
Functioning Among Humans
Tobias Pfeiffer
 
Functioning Among Humans
Functioning Among HumansFunctioning Among Humans
Functioning Among Humans
Tobias Pfeiffer
 
Elixir, your Monolith and You
Elixir, your Monolith and YouElixir, your Monolith and You
Elixir, your Monolith and You
Tobias 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
 
Where do Rubyists go?
 Where do Rubyists go?  Where do Rubyists go?
Where do Rubyists go?
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 practice
Tobias Pfeiffer
 
Introducing Elixir the easy way
Introducing Elixir the easy wayIntroducing Elixir the easy way
Introducing Elixir the easy way
Tobias Pfeiffer
 
Elixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitElixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicit
Tobias 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 explicit
Tobias 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
 

More from 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?
 

Recently uploaded

(CISOPlatform Summit & SACON 2024) Gen AI & Deepfake In Overall Security.pdf
(CISOPlatform Summit & SACON 2024) Gen AI & Deepfake In Overall Security.pdf(CISOPlatform Summit & SACON 2024) Gen AI & Deepfake In Overall Security.pdf
(CISOPlatform Summit & SACON 2024) Gen AI & Deepfake In Overall Security.pdf
Priyanka Aash
 
BLOCKCHAIN TECHNOLOGY - Advantages and Disadvantages
BLOCKCHAIN TECHNOLOGY - Advantages and DisadvantagesBLOCKCHAIN TECHNOLOGY - Advantages and Disadvantages
BLOCKCHAIN TECHNOLOGY - Advantages and Disadvantages
SAI KAILASH R
 
BT & Neo4j: Knowledge Graphs for Critical Enterprise Systems.pptx.pdf
BT & Neo4j: Knowledge Graphs for Critical Enterprise Systems.pptx.pdfBT & Neo4j: Knowledge Graphs for Critical Enterprise Systems.pptx.pdf
BT & Neo4j: Knowledge Graphs for Critical Enterprise Systems.pptx.pdf
Neo4j
 
Integrating Kafka with MuleSoft 4 and usecase
Integrating Kafka with MuleSoft 4 and usecaseIntegrating Kafka with MuleSoft 4 and usecase
Integrating Kafka with MuleSoft 4 and usecase
shyamraj55
 
TrustArc Webinar - 2024 Data Privacy Trends: A Mid-Year Check-In
TrustArc Webinar - 2024 Data Privacy Trends: A Mid-Year Check-InTrustArc Webinar - 2024 Data Privacy Trends: A Mid-Year Check-In
TrustArc Webinar - 2024 Data Privacy Trends: A Mid-Year Check-In
TrustArc
 
Semantic-Aware Code Model: Elevating the Future of Software Development
Semantic-Aware Code Model: Elevating the Future of Software DevelopmentSemantic-Aware Code Model: Elevating the Future of Software Development
Semantic-Aware Code Model: Elevating the Future of Software Development
Baishakhi Ray
 
Girls call Kolkata 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
Girls call Kolkata 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery Girls call Kolkata 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
Girls call Kolkata 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
sunilverma7884
 
(CISOPlatform Summit & SACON 2024) Orientation by CISO Platform_ Using CISO P...
(CISOPlatform Summit & SACON 2024) Orientation by CISO Platform_ Using CISO P...(CISOPlatform Summit & SACON 2024) Orientation by CISO Platform_ Using CISO P...
(CISOPlatform Summit & SACON 2024) Orientation by CISO Platform_ Using CISO P...
Priyanka Aash
 
Opencast Summit 2024 — Opencast @ University of Münster
Opencast Summit 2024 — Opencast @ University of MünsterOpencast Summit 2024 — Opencast @ University of Münster
Opencast Summit 2024 — Opencast @ University of Münster
Matthias Neugebauer
 
(CISOPlatform Summit & SACON 2024) Digital Personal Data Protection Act.pdf
(CISOPlatform Summit & SACON 2024) Digital Personal Data Protection Act.pdf(CISOPlatform Summit & SACON 2024) Digital Personal Data Protection Act.pdf
(CISOPlatform Summit & SACON 2024) Digital Personal Data Protection Act.pdf
Priyanka Aash
 
High Profile Girls Call ServiCe Hyderabad 0000000000 Tanisha Best High Class ...
High Profile Girls Call ServiCe Hyderabad 0000000000 Tanisha Best High Class ...High Profile Girls Call ServiCe Hyderabad 0000000000 Tanisha Best High Class ...
High Profile Girls Call ServiCe Hyderabad 0000000000 Tanisha Best High Class ...
aslasdfmkhan4750
 
"Mastering Graphic Design: Essential Tips and Tricks for Beginners and Profes...
"Mastering Graphic Design: Essential Tips and Tricks for Beginners and Profes..."Mastering Graphic Design: Essential Tips and Tricks for Beginners and Profes...
"Mastering Graphic Design: Essential Tips and Tricks for Beginners and Profes...
Anant Gupta
 
Dublin_mulesoft_meetup_Mulesoft_Salesforce_Integration (1).pptx
Dublin_mulesoft_meetup_Mulesoft_Salesforce_Integration (1).pptxDublin_mulesoft_meetup_Mulesoft_Salesforce_Integration (1).pptx
Dublin_mulesoft_meetup_Mulesoft_Salesforce_Integration (1).pptx
Kunal Gupta
 
leewayhertz.com-AI agents for healthcare Applications benefits and implementa...
leewayhertz.com-AI agents for healthcare Applications benefits and implementa...leewayhertz.com-AI agents for healthcare Applications benefits and implementa...
leewayhertz.com-AI agents for healthcare Applications benefits and implementa...
alexjohnson7307
 
Three New Criminal Laws in India 1 July 2024
Three New Criminal Laws in India 1 July 2024Three New Criminal Laws in India 1 July 2024
Three New Criminal Laws in India 1 July 2024
aakash malhotra
 
(CISOPlatform Summit & SACON 2024) Cyber Insurance & Risk Quantification.pdf
(CISOPlatform Summit & SACON 2024) Cyber Insurance & Risk Quantification.pdf(CISOPlatform Summit & SACON 2024) Cyber Insurance & Risk Quantification.pdf
(CISOPlatform Summit & SACON 2024) Cyber Insurance & Risk Quantification.pdf
Priyanka Aash
 
EuroPython 2024 - Streamlining Testing in a Large Python Codebase
EuroPython 2024 - Streamlining Testing in a Large Python CodebaseEuroPython 2024 - Streamlining Testing in a Large Python Codebase
EuroPython 2024 - Streamlining Testing in a Large Python Codebase
Jimmy Lai
 
Use Cases & Benefits of RPA in Manufacturing in 2024.pptx
Use Cases & Benefits of RPA in Manufacturing in 2024.pptxUse Cases & Benefits of RPA in Manufacturing in 2024.pptx
Use Cases & Benefits of RPA in Manufacturing in 2024.pptx
SynapseIndia
 
Introduction-to-the-IAM-Platform-Implementation-Plan.pptx
Introduction-to-the-IAM-Platform-Implementation-Plan.pptxIntroduction-to-the-IAM-Platform-Implementation-Plan.pptx
Introduction-to-the-IAM-Platform-Implementation-Plan.pptx
313mohammedarshad
 
(CISOPlatform Summit & SACON 2024) Keynote _ Power Digital Identities With AI...
(CISOPlatform Summit & SACON 2024) Keynote _ Power Digital Identities With AI...(CISOPlatform Summit & SACON 2024) Keynote _ Power Digital Identities With AI...
(CISOPlatform Summit & SACON 2024) Keynote _ Power Digital Identities With AI...
Priyanka Aash
 

Recently uploaded (20)

(CISOPlatform Summit & SACON 2024) Gen AI & Deepfake In Overall Security.pdf
(CISOPlatform Summit & SACON 2024) Gen AI & Deepfake In Overall Security.pdf(CISOPlatform Summit & SACON 2024) Gen AI & Deepfake In Overall Security.pdf
(CISOPlatform Summit & SACON 2024) Gen AI & Deepfake In Overall Security.pdf
 
BLOCKCHAIN TECHNOLOGY - Advantages and Disadvantages
BLOCKCHAIN TECHNOLOGY - Advantages and DisadvantagesBLOCKCHAIN TECHNOLOGY - Advantages and Disadvantages
BLOCKCHAIN TECHNOLOGY - Advantages and Disadvantages
 
BT & Neo4j: Knowledge Graphs for Critical Enterprise Systems.pptx.pdf
BT & Neo4j: Knowledge Graphs for Critical Enterprise Systems.pptx.pdfBT & Neo4j: Knowledge Graphs for Critical Enterprise Systems.pptx.pdf
BT & Neo4j: Knowledge Graphs for Critical Enterprise Systems.pptx.pdf
 
Integrating Kafka with MuleSoft 4 and usecase
Integrating Kafka with MuleSoft 4 and usecaseIntegrating Kafka with MuleSoft 4 and usecase
Integrating Kafka with MuleSoft 4 and usecase
 
TrustArc Webinar - 2024 Data Privacy Trends: A Mid-Year Check-In
TrustArc Webinar - 2024 Data Privacy Trends: A Mid-Year Check-InTrustArc Webinar - 2024 Data Privacy Trends: A Mid-Year Check-In
TrustArc Webinar - 2024 Data Privacy Trends: A Mid-Year Check-In
 
Semantic-Aware Code Model: Elevating the Future of Software Development
Semantic-Aware Code Model: Elevating the Future of Software DevelopmentSemantic-Aware Code Model: Elevating the Future of Software Development
Semantic-Aware Code Model: Elevating the Future of Software Development
 
Girls call Kolkata 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
Girls call Kolkata 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery Girls call Kolkata 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
Girls call Kolkata 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
 
(CISOPlatform Summit & SACON 2024) Orientation by CISO Platform_ Using CISO P...
(CISOPlatform Summit & SACON 2024) Orientation by CISO Platform_ Using CISO P...(CISOPlatform Summit & SACON 2024) Orientation by CISO Platform_ Using CISO P...
(CISOPlatform Summit & SACON 2024) Orientation by CISO Platform_ Using CISO P...
 
Opencast Summit 2024 — Opencast @ University of Münster
Opencast Summit 2024 — Opencast @ University of MünsterOpencast Summit 2024 — Opencast @ University of Münster
Opencast Summit 2024 — Opencast @ University of Münster
 
(CISOPlatform Summit & SACON 2024) Digital Personal Data Protection Act.pdf
(CISOPlatform Summit & SACON 2024) Digital Personal Data Protection Act.pdf(CISOPlatform Summit & SACON 2024) Digital Personal Data Protection Act.pdf
(CISOPlatform Summit & SACON 2024) Digital Personal Data Protection Act.pdf
 
High Profile Girls Call ServiCe Hyderabad 0000000000 Tanisha Best High Class ...
High Profile Girls Call ServiCe Hyderabad 0000000000 Tanisha Best High Class ...High Profile Girls Call ServiCe Hyderabad 0000000000 Tanisha Best High Class ...
High Profile Girls Call ServiCe Hyderabad 0000000000 Tanisha Best High Class ...
 
"Mastering Graphic Design: Essential Tips and Tricks for Beginners and Profes...
"Mastering Graphic Design: Essential Tips and Tricks for Beginners and Profes..."Mastering Graphic Design: Essential Tips and Tricks for Beginners and Profes...
"Mastering Graphic Design: Essential Tips and Tricks for Beginners and Profes...
 
Dublin_mulesoft_meetup_Mulesoft_Salesforce_Integration (1).pptx
Dublin_mulesoft_meetup_Mulesoft_Salesforce_Integration (1).pptxDublin_mulesoft_meetup_Mulesoft_Salesforce_Integration (1).pptx
Dublin_mulesoft_meetup_Mulesoft_Salesforce_Integration (1).pptx
 
leewayhertz.com-AI agents for healthcare Applications benefits and implementa...
leewayhertz.com-AI agents for healthcare Applications benefits and implementa...leewayhertz.com-AI agents for healthcare Applications benefits and implementa...
leewayhertz.com-AI agents for healthcare Applications benefits and implementa...
 
Three New Criminal Laws in India 1 July 2024
Three New Criminal Laws in India 1 July 2024Three New Criminal Laws in India 1 July 2024
Three New Criminal Laws in India 1 July 2024
 
(CISOPlatform Summit & SACON 2024) Cyber Insurance & Risk Quantification.pdf
(CISOPlatform Summit & SACON 2024) Cyber Insurance & Risk Quantification.pdf(CISOPlatform Summit & SACON 2024) Cyber Insurance & Risk Quantification.pdf
(CISOPlatform Summit & SACON 2024) Cyber Insurance & Risk Quantification.pdf
 
EuroPython 2024 - Streamlining Testing in a Large Python Codebase
EuroPython 2024 - Streamlining Testing in a Large Python CodebaseEuroPython 2024 - Streamlining Testing in a Large Python Codebase
EuroPython 2024 - Streamlining Testing in a Large Python Codebase
 
Use Cases & Benefits of RPA in Manufacturing in 2024.pptx
Use Cases & Benefits of RPA in Manufacturing in 2024.pptxUse Cases & Benefits of RPA in Manufacturing in 2024.pptx
Use Cases & Benefits of RPA in Manufacturing in 2024.pptx
 
Introduction-to-the-IAM-Platform-Implementation-Plan.pptx
Introduction-to-the-IAM-Platform-Implementation-Plan.pptxIntroduction-to-the-IAM-Platform-Implementation-Plan.pptx
Introduction-to-the-IAM-Platform-Implementation-Plan.pptx
 
(CISOPlatform Summit & SACON 2024) Keynote _ Power Digital Identities With AI...
(CISOPlatform Summit & SACON 2024) Keynote _ Power Digital Identities With AI...(CISOPlatform Summit & SACON 2024) Keynote _ Power Digital Identities With AI...
(CISOPlatform Summit & SACON 2024) Keynote _ Power Digital Identities With AI...
 

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!
  • 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!
  • 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???
  • 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
  • 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?