Slide 1

Slide 1 text

Thanks for making it!

Slide 2

Slide 2 text

The magic hook

Slide 3

Slide 3 text

Event Form

Slide 4

Slide 4 text

Repetitive

Slide 5

Slide 5 text

Much better!

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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?

Slide 14

Slide 14 text

# It works!

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

let(:event) do build :event, ends_at: Time.zone.local(2042, 1, 1, 15, 45) end

Slide 17

Slide 17 text

let(:event) do build :event, ends_at: Time.zone.local(2042, 1, 1, 15, 45) end

Slide 18

Slide 18 text

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!

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

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!

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Validations

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Why the effort?

Slide 28

Slide 28 text

Practice open?

Slide 29

Slide 29 text

Practice open? No overlap?

Slide 30

Slide 30 text

Practice open? No overlap? Right skills?

Slide 31

Slide 31 text

Practice open? No overlap? Right skills? Patient can be contacted?

Slide 32

Slide 32 text

Practice open? No overlap? Right skills? Patient can be contacted? Associated models

Slide 33

Slide 33 text

Practice open? No overlap? Right skills? Patient can be contacted? Associated models ...

Slide 34

Slide 34 text

Expensive Test Setup

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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?

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Why are we doing this?

Slide 41

Slide 41 text

Affordance

Slide 42

Slide 42 text

Wants to be cuddled

Slide 43

Slide 43 text

Wants to be fed

Slide 44

Slide 44 text

class MySolution def do_thing(argument) end end OOP Affordance

Slide 45

Slide 45 text

Model View Controller Rails Affordance

Slide 46

Slide 46 text

“Fat Models Skinny Controllers”

Slide 47

Slide 47 text

1 or 2 use cases stuck on every model

Slide 48

Slide 48 text

Controllers Models

Slide 49

Slide 49 text

Registrations User

Slide 50

Slide 50 text

Registrations User Users

Slide 51

Slide 51 text

Registrations User Users Users ##. ##. ##.

Slide 52

Slide 52 text

Controllers Models Service Objects

Slide 53

Slide 53 text

Registrations User Users Users ##. ##. ##. ##. ##. ##. ##. ##.

Slide 54

Slide 54 text

Registrations User Users Users ##. ##. ##. ##. ##. ##. ##. ##. Business Logic Separated

Slide 55

Slide 55 text

Registrations User Users Users ##. ##. ##. ##. ##. ##. ##. ##. Business Logic Separated Validations and Callbacks still mixed

Slide 56

Slide 56 text

Registrations User Users Users ##. ##. ##. ##. ##. ##. ##. ##. Business Logic Separated Validations and Callbacks still mixed Run all the time by default

Slide 57

Slide 57 text

Registrations User Users Users ##. ##. ##. ##. ##. ##. ##. ##. SignUp Show Index ##. ##. ##.

Slide 58

Slide 58 text

Registrations User ##. SignUp View Controller Service Model

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Registrations User ##. SignUp View Controller Service Model Knowledge?

Slide 64

Slide 64 text

Registrations User ##. SignUp View Controller Service Model Why Solve it here?

Slide 65

Slide 65 text

Registrations User ##. SignUp View Controller Service Model Opt Out

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

Registrations User ##. SignUp View Controller Service Model Could solve here

Slide 69

Slide 69 text

Registrations User ##. SignUp View Controller Service Model Or here?

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

Tobi complaining about validations and callbacks You’ve seen:

Slide 72

Slide 72 text

Do You Need That Validation? Let Me Call You Back About It Tobias Pfeiffer @PragTob pragtob.info

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

Specifics clutter Model

Slide 75

Slide 75 text

Specifics clutter Model Hard to get overiew

Slide 76

Slide 76 text

Specifics clutter Model Hard to get overiew Run all the time

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

What does Rails offer?

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

class Person < ApplicationRecord validates :email, uniqueness: true, on: :account_setup validates :age, numericality: true, on: :account_setup end Custom Contexts

Slide 82

Slide 82 text

What’s out there?

Slide 83

Slide 83 text

Form Objects

Slide 84

Slide 84 text

Form Objects Form Objects Form 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

Slide 85

Slide 85 text

Form Objects Form Objects Plain 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

Slide 86

Slide 86 text

Form Objects Form Objects Validations 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

Slide 87

Slide 87 text

Form Objects Form Objects Attributes 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

Slide 88

Slide 88 text

Form Objects Form Objects Map 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

Slide 89

Slide 89 text

Form Objects Form Objects Interface 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

Slide 90

Slide 90 text

Form Objects Form Objects Callbacks 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

Slide 91

Slide 91 text

def create @user = Registration.new(registration_params) if @user.save # ##. else # ##. end end Same Interface

Slide 92

Slide 92 text

Inheritance!

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

Changesets

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

“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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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?

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

defmodule ValidationShowcase.Accounts do def create_user(attrs \\ %{}) do %User{} |> User.registration_changeset(attrs) |> Repo.insert() |> send_welcome_email() end end Context

Slide 111

Slide 111 text

defmodule ValidationShowcase.Accounts do def create_user(attrs \\ %{}) do %User{} |> User.registration_changeset(attrs) |> Repo.insert() |> send_welcome_email() end end Changeset

Slide 112

Slide 112 text

defmodule ValidationShowcase.Accounts do def create_user(attrs \\ %{}) do %User{} |> User.registration_changeset(attrs) |> Repo.insert() |> send_welcome_email() end end “after_commit”

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

Form Objects Form Objects Separate Operations and Validators

Slide 115

Slide 115 text

trailblazer

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

“Models are persistence-only and solely define associations and scopes. No business code is to be found here. No validations, no callbacks.” trailblazer

Slide 131

Slide 131 text

The architecture eases keeping the business logic (entities) separated from details such as persistence or validations. hanami/model

Slide 132

Slide 132 text

Takeaway

Slide 133

Slide 133 text

I don’t hate Rails

Slide 134

Slide 134 text

I don’t hate Rails Future in Rails?

Slide 135

Slide 135 text

I don’t hate Rails Affordances Future in Rails?

Slide 136

Slide 136 text

I don’t hate Rails Alternatives Affordances Future in Rails?

Slide 137

Slide 137 text

Form Objects

Slide 138

Slide 138 text

Inheritance!

Slide 139

Slide 139 text

Changesets

Slide 140

Slide 140 text

Form Objects Form Objects Separate Operations and Validators

Slide 141

Slide 141 text

I don’t hate Rails Careful with validations and callbacks Alternatives Affordances Future in Rails?

Slide 142

Slide 142 text

Thank you!