Slide 1

Slide 1 text

Models Models Every Where The Rime of the ActiveModel

Slide 2

Slide 2 text

SCALE MODEL?

Slide 3

Slide 3 text

MODEL HOME?

Slide 4

Slide 4 text

TYRA BANKS?

Slide 5

Slide 5 text

What are models?

Slide 6

Slide 6 text

app/models

Slide 7

Slide 7 text

ActiveRecord

Slide 8

Slide 8 text

Rails + Merb = 3.0

Slide 9

Slide 9 text

The Great Decoupling ActiveRecord ActiveResource Sequel DataMapper Mongoid MongoMapper

Slide 10

Slide 10 text

Why app/models?

Slide 11

Slide 11 text

MODEL VIEW CONTROLLER (MVC) SMALLTALK AT XEROX PARC (1979)

Slide 12

Slide 12 text

GUI Architectures Model View Controller Model View Adapter Model View Presenter Model View ViewModel Hierarchical Model View Controller

Slide 13

Slide 13 text

Models, models every where Entities Value Objects Request Models Response Models Presentation Models

Slide 14

Slide 14 text

“Model objects are the things in the system that you are trying to model.” David Heinemeier Hansson, 2004

Slide 15

Slide 15 text

But what are models?

Slide 16

Slide 16 text

MODEL VIEW CONTROLLER (MVC) SMALLTALK AT XEROX PARC (1979)

Slide 17

Slide 17 text

MODELS REPRESENT KNOWLEDGE MODELS = KNOWLEDGE, KNOWLEDGE = POWER, MODELS = POWER

Slide 18

Slide 18 text

MODELS AREN’T DEFINED BY PERSISTENCE THE ELEPHANT IN THE ROOM

Slide 19

Slide 19 text

Examples of Models in Rails Rack::Request Rails::Application ActiveModel::Errors ActiveResource::Connection ActiveRecord::Associations::BelongsToAssociation

Slide 20

Slide 20 text

SPOTTING MISSING MODELS

Slide 21

Slide 21 text

WHERE DO MODELS HIDE? UNDER YOUR NOSE

Slide 22

Slide 22 text

MODELS HIDE IN CONTROLLERS FAT CONTROLLERS?

Slide 23

Slide 23 text

MODELS HIDE IN VIEWS LOGIC LADEN VIEWS?

Slide 24

Slide 24 text

MODELS HIDE IN OTHER MODELS MODELS WITH TOO MUCH RESPONSIBILITY

Slide 25

Slide 25 text

MODELS HIDE BITS OF THEMSELVES ALL OVER SPREAD IT AROUND

Slide 26

Slide 26 text

SIGNS TO WATCH FOR

Slide 27

Slide 27 text

MODEL VIEW CONTROLLER VIEWS AND CONTROLLERS WITHOUT A CLEAR SOURCE OF KNOWLEDGE X

Slide 28

Slide 28 text

PRIMITIVE OBSESSION USING BUILT-IN TYPES TO DO A CLASS’ JOB

Slide 29

Slide 29 text

LARGE CLASS MULTIPLE RESPONSIBILITIES

Slide 30

Slide 30 text

Ideas for Models Address Contact Form Web Service Request/Response Presentation/View Model

Slide 31

Slide 31 text

Example LoginRequest

Slide 32

Slide 32 text

app/views/sessions/new.html.erb <% flash.each do |name, message| %> <%= content_tag(:p, message) %> <% end %>

Log In

<%= form_tag session_path do %>
<%= label_tag 'email' %>
<%= text_field_tag 'email', @email %>
<%= label_tag 'password' %>
<%= password_field_tag 'password', nil %>
<%= link_to 'Forgot Password?', password_help_path %>
<%= check_box_tag 'remember_me', '1', @remember_me.nil? ? true : @remember_me %> <%= label_tag 'remember_me' %>
<%= submit_tag 'Log in' %>
<% end %>

Slide 33

Slide 33 text

app/views/sessions/new.html.erb <% flash.each do |name, message| %> <%= content_tag(:p, message) %> <% end %>

Log In

<%= form_tag session_path do %> <%# … %> <% end %>

Slide 34

Slide 34 text

app/views/sessions/new.html.erb
<%= label_tag 'email' %>
<%= text_field_tag 'email', @email %>

Slide 35

Slide 35 text

app/views/sessions/new.html.erb
<%= label_tag 'password' %>
<%= password_field_tag 'password', nil %>
<%= link_to 'Forgot Password?', password_help_path %>

Slide 36

Slide 36 text

app/views/sessions/new.html.erb
<%= check_box_tag 'remember_me', '1', @remember_me.nil? ? true : @remember_me %> <%= label_tag 'remember_me' %>

Slide 37

Slide 37 text

app/views/sessions/new.html.erb
<%= submit_tag 'Log in' %>

Slide 38

Slide 38 text

app/controllers/sessions_controller.rb class SessionsController < ApplicationController def new end def create logout_keeping_session! user = User.authenticate(params[:email], params[:password]) if user self.current_user = user handle_remember_cookie! (params[:remember_me] == "1") flash[:notice] = "Logged in successfully." redirect_back_or_default root_url else errors = [] errors << "Email can't be blank" if params[:email].blank? errors << "Password can't be blank" if params[:password].blank? errors << "Password is too short (minimum is 5 characters)" if params[:password].present? && params[:password].length < 5 flash.now[:error] = errors.any? ? errors.join(" ") : "Couldn't log you in as '#{params[:email]}'" logger.warn "Failed signin for '#{params[:email]}' at #{Time.now.utc}" @email = params[:email] @remember_me = !!params[:remember_me] render :new end end end

Slide 39

Slide 39 text

app/controllers/sessions_controller.rb def new end

Slide 40

Slide 40 text

app/controllers/sessions_controller.rb def create logout_keeping_session!

Slide 41

Slide 41 text

app/controllers/sessions_controller.rb user = User.authenticate(params[:email], params[:password])

Slide 42

Slide 42 text

app/controllers/sessions_controller.rb if user self.current_user = user handle_remember_cookie! (params[:remember_me] == "1") flash[:notice] = "Logged in successfully." redirect_back_or_default root_url else

Slide 43

Slide 43 text

app/controllers/sessions_controller.rb errors = [] errors << "Email can't be blank" if params[:email].blank? errors << "Password can't be blank" if params[:password].blank? errors << "Password is too short (minimum is 5 characters)" if params[:password].present? && params[:password].length < 5

Slide 44

Slide 44 text

app/controllers/sessions_controller.rb flash.now[:error] = errors.any? ? errors.join(" ") : "Couldn't log you in as '#{params[:email]}'" logger.warn "Failed signin for '#{params[:email]}' at #{Time.now.utc}"

Slide 45

Slide 45 text

app/controllers/sessions_controller.rb @email = params[:email] @remember_me = !!params[:remember_me] render :new

Slide 46

Slide 46 text

CODE SMELLS

Slide 47

Slide 47 text

MODEL VIEW CONTROLLER VIEWS AND CONTROLLERS WITHOUT A CLEAR SOURCE OF KNOWLEDGE X

Slide 48

Slide 48 text

SCATTERED KNOWLEDGE KNOWLEDGE IN CONTROLLERS AND VIEWS

Slide 49

Slide 49 text

Refactor with Caution Refactor with Tests

Slide 50

Slide 50 text

features/login.feature Feature: Logging in As an anonymous user with an account I want to log in to my account So that I can be myself Background: Given the following user exists: | first_name | last_name | email | password | | President | Skroob | [email protected] | 12345 | And I am on the login page

Slide 51

Slide 51 text

features/login.feature Scenario: Login successfully When I fill in "Email" with "[email protected]" And I fill in "Password" with "12345" And the "Remember me" checkbox should be checked And I press "Log in" Then I should see "Logged in successfully"

Slide 52

Slide 52 text

$ cucumber features/login.feature Using the default profile... ......... 1 scenario (1 passed) 7 steps (7 passed) 0m0.971s

Slide 53

Slide 53 text

features/login.feature Scenario: Submit blank form When I uncheck "Remember me" And I press "Log in" Then I should see "Email can't be blank" And I should see "Password can't be blank" And I should not see "Couldn't log you in" And I should not see "Password is too short" And the "Remember me" checkbox should not be checked

Slide 54

Slide 54 text

$ cucumber features/login.feature Using the default profile... .................. 2 scenarios (2 passed) 16 steps (16 passed) 0m1.157s

Slide 55

Slide 55 text

features/login.feature Scenario: Password too short When I fill in "Email" with "[email protected]" And I fill in "Password" with "1234" And I press "Log in" Then I should see "Password is too short (minimum is 5 characters)" And I should not see "Couldn't log you in as '[email protected]'" And the "Password" field should not contain "1234"

Slide 56

Slide 56 text

$ cucumber features/login.feature Using the default profile... .......................... 3 scenarios (3 passed) 24 steps (24 passed) 0m1.409s

Slide 57

Slide 57 text

features/login.feature Scenario: Incorrect password When I fill in "Email" with "[email protected]" And I fill in "Password" with "54321" And I press "Log in" Then I should see "Couldn't log you in as '[email protected]'" And the "Email" field should contain "[email protected]" And the "Password" field should not contain "54321"

Slide 58

Slide 58 text

$ cucumber features/login.feature Using the default profile... .................................. 4 scenarios (4 passed) 32 steps (32 passed) 0m1.563s

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

A New View On Life

Log In

<%= form_for @login_request, :url => session_path do |f| %> <% @login_request.errors.full_messages.each do |message| %> <%= content_tag(:p, message) %> <% end %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= link_to 'Forgot Password?', password_help_path %>
<%= f.check_box :remember_me %> <%= f.label :remember_me %>
<%= f.submit "Log in" %>
<% end %>

Slide 61

Slide 61 text

Before <% flash.each do |name, message| %> <%= content_tag(:p, message) %> <% end %>

Log In

<%= form_tag session_path do %> <%# … %> <% end %>

Slide 62

Slide 62 text

After

Log In

<%= form_for @login_request, :url => session_path do |f| %> <% @login_request.errors.full_messages.each do |message| %> <%= content_tag(:p, message) %> <% end %> <%# … %> <% end %>

Slide 63

Slide 63 text

Before
<%= label_tag 'email' %>
<%= text_field_tag 'email', @email %>

Slide 64

Slide 64 text

After
<%= f.label :email %>
<%= f.text_field :email %>

Slide 65

Slide 65 text

Before
<%= label_tag 'password' %>
<%= password_field_tag 'password', nil %>
<%= link_to 'Forgot Password?', password_help_path %>

Slide 66

Slide 66 text

After
<%= f.label :password %>
<%= f.password_field :password %>
<%= link_to 'Forgot Password?', password_help_path %>

Slide 67

Slide 67 text

Before
<%= check_box_tag 'remember_me', '1', @remember_me.nil? ? true : @remember_me %> <%= label_tag 'remember_me' %>

Slide 68

Slide 68 text

After
<%= f.check_box :remember_me %> <%= f.label :remember_me %>

Slide 69

Slide 69 text

Before
<%= submit_tag 'Log in' %>

Slide 70

Slide 70 text

After
<%= f.submit "Log in" %>

Slide 71

Slide 71 text

Regaining Control class SessionsController < ApplicationController def new @login_request = LoginRequest.new end def create logout_keeping_session! @login_request = LoginRequest.new(params[:login_request]) if @login_request.authenticate self.current_user = @login_request.user handle_remember_cookie! @login_request.remember_me? flash[:notice] = "Logged in successfully." redirect_back_or_default root_url else render :new end end end

Slide 72

Slide 72 text

Before def new end

Slide 73

Slide 73 text

After def new @login_request = LoginRequest.new end

Slide 74

Slide 74 text

Before & After def create logout_keeping_session!

Slide 75

Slide 75 text

Before user = User.authenticate(params[:email], params[:password])

Slide 76

Slide 76 text

After @login_request = LoginRequest.new(params[:login_request])

Slide 77

Slide 77 text

Before if user self.current_user = user handle_remember_cookie! (params[:remember_me] == "1") flash[:notice] = "Logged in successfully." redirect_back_or_default root_url else

Slide 78

Slide 78 text

After if @login_request.authenticate self.current_user = @login_request.user handle_remember_cookie! @login_request.remember_me? flash[:notice] = "Logged in successfully." redirect_back_or_default root_url else

Slide 79

Slide 79 text

Before Part 1 errors = [] errors << "Email can't be blank" if params[:email].blank? errors << "Password can't be blank" if params[:password].blank? errors << "Password is too short (minimum is 5 characters)" if params[:password].present? && params[:password].length < 5

Slide 80

Slide 80 text

Before Part 2 flash.now[:error] = errors.any? ? errors.join(" ") : "Couldn't log you in as '#{params[:email]}'" logger.warn "Failed signin for '#{params[:email]}' at #{Time.now.utc}"

Slide 81

Slide 81 text

Before Part 3 @email = params[:email] @remember_me = !!params[:remember_me] render :new

Slide 82

Slide 82 text

After render :new

Slide 83

Slide 83 text

NOW WE LET THE TESTS TAKE THE WHEEL TEST DRIVEN DEVELOPMENT

Slide 84

Slide 84 text

$ cucumber features/login.feature Using the default profile... .F.F------------------------------ (::) failed steps (::) uninitialized constant SessionsController::LoginRequest (NameError)

Slide 85

Slide 85 text

app/models/login_request.rb class LoginRequest end

Slide 86

Slide 86 text

$ cucumber features/login.feature Using the default profile... .F.F------------------------------ (::) failed steps (::) undefined method `model_name' for LoginRequest:Class (ActionView::Template::Error

Slide 87

Slide 87 text

The ActiveModel API #to_model

Slide 88

Slide 88 text

ActiveModel API #persisted? #valid? #errors #[] #full_messages #to_key #to_param #to_partial_path .model_name #human #singular #plural

Slide 89

Slide 89 text

When does a model need to be an ActiveModel? Railties ActiveSupport ActiveRecord ActiveResource ActiveModel ActionMailer

Slide 90

Slide 90 text

ActiveModel API is for ActionPack

Slide 91

Slide 91 text

When does ActionPack need an ActiveModel? Polymorphic Routes (url_for) Partial Rendering (render) Record Identification (dom_class, dom_id) Form Building (form_for)

Slide 92

Slide 92 text

ActiveModel::Lint::Tests

Slide 93

Slide 93 text

$ rspec spec/models/ login_request_spec.rb FFFFFFF Failures: 1) LoginRequest it should behave like ActiveModel test model naming Failure/Error: send test Test::Unit::AssertionFailedError: The object should respond_to to_model. is not true.

Slide 94

Slide 94 text

app/models/login_request.rb class LoginRequest def to_model self end end

Slide 95

Slide 95 text

$ rspec spec/models/ login_request_spec.rb FFFFFFF Failures: 1) LoginRequest it should behave like ActiveModel test model naming Failure/Error: send test Test::Unit::AssertionFailedError: The object should respond_to model_name. is not true.

Slide 96

Slide 96 text

The ActiveModel Library gem install activemodel

Slide 97

Slide 97 text

ActiveModel::Naming .model_name #human #singular #plural

Slide 98

Slide 98 text

app/models/login_request.rb class LoginRequest extend ActiveModel::Naming def to_model self end end

Slide 99

Slide 99 text

$ rspec spec/models/ login_request_spec.rb .FFFFFF Failures: 1) LoginRequest it should behave like ActiveModel test to param Failure/Error: send test Test::Unit::AssertionFailedError: to_param should return nil when `persisted?` returns false. is not true.

Slide 100

Slide 100 text

ActiveModel::Conversion #to_model #to_key #to_param

Slide 101

Slide 101 text

app/models/login_request.rb class LoginRequest extend ActiveModel::Naming include ActiveModel::Conversion end

Slide 102

Slide 102 text

$ rspec spec/models/ login_request_spec.rb ...FFFF Failures: 1) LoginRequest it should behave like ActiveModel test valid? Failure/Error: send test Test::Unit::AssertionFailedError: The model should respond to valid?. is not true.

Slide 103

Slide 103 text

ActiveModel::Validations #valid? #errors .validates_acceptance_of .validates_confirmation_of .validates_exclusion_of .validates_format_of .validates_inclusion_of .validates_length_of .validates_numericality_of .validates_presence_of

Slide 104

Slide 104 text

app/models/login_request.rb class LoginRequest extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations end

Slide 105

Slide 105 text

$ rspec spec/models/ login_request_spec.rb ......F Failures: 1) LoginRequest it should behave like ActiveModel test persisted? Failure/Error: send test Test::Unit::AssertionFailedError: The model should respond to persisted?. is not true.

Slide 106

Slide 106 text

app/models/login_request.rb class LoginRequest extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations def persisted? false end end

Slide 107

Slide 107 text

$ rspec spec/models/ login_request_spec.rb ....... Finished in 0.00835 seconds 7 examples, 0 failures

Slide 108

Slide 108 text

$ cucumber features/login.feature Using the default profile... .F.F------------------------------ (::) failed steps (::) undefined method `email' for # (ActionView::Template::Error)

Slide 109

Slide 109 text

app/models/login_request.rb attr_accessor :email, :password, :remember_me

Slide 110

Slide 110 text

$ cucumber features/login.feature Using the default profile... ......F--...F-----....F---....F--- (::) failed steps (::) expected false to be true features/login.feature:15:in `And the "Remember me" checkbox should be checked'

Slide 111

Slide 111 text

app/models/login_request.rb def initialize self.remember_me = true end

Slide 112

Slide 112 text

$ cucumber features/login.feature Using the default profile... .......F-...F-----....F---....F--- (::) failed steps (::) wrong number of arguments (1 for 0) (ArgumentError) ./app/controllers/ sessions_controller.rb:10:in `initialize'

Slide 113

Slide 113 text

app/controllers/sessions_controller.rb @login_request = LoginRequest.new(params[:login_request])

Slide 114

Slide 114 text

app/models/login_request.rb def initialize(new_attributes={}) self.remember_me = true new_attributes.each do |attribute, value| if respond_to?("#{attribute}=") send("#{attribute}=", value) end end end

Slide 115

Slide 115 text

$ cucumber features/login.feature Using the default profile... .......F-...F-----....F---....F--- (::) failed steps (::) undefined method `authenticate' for # (NoMethodError) ./app/controllers/ sessions_controller.rb:12:in `create'

Slide 116

Slide 116 text

app/models/login_request.rb def authenticate success = valid? logger.warn "Failed signin for #{email.inspect}" unless success success end def logger Rails.logger end

Slide 117

Slide 117 text

$ cucumber features/login.feature Using the default profile... .......F-...F-----....F---....F--- (::) failed steps (::) undefined method `user' for # (NoMethodError) ./app/controllers/ sessions_controller.rb:13:in `create'

Slide 118

Slide 118 text

app/models/login_request.rb def user return @user if instance_variable_defined? "@user" @user = User.authenticate(email, password) end

Slide 119

Slide 119 text

$ cucumber features/login.feature Using the default profile... .......F-...F-----....F---....F--- (::) failed steps (::) undefined method `remember_me?' for # (NoMethodError) ./app/controllers/ sessions_controller.rb:14:in `create'

Slide 120

Slide 120 text

app/models/login_request.rb def remember_me? remember_me.present? end

Slide 121

Slide 121 text

$ cucumber features/login.feature Using the default profile... .............F----.....F--.....F-- (::) failed steps (::) expected there to be content "Email can't be blank"

Slide 122

Slide 122 text

app/models/login_request.rb validates_presence_of :email, :password validates_length_of :password, :minimum => 5, :allow_blank => true

Slide 123

Slide 123 text

$ cucumber features/login.feature Using the default profile... ...............................F-- (::) failed steps (::) expected there to be content "Couldn't log you in as '[email protected]'"

Slide 124

Slide 124 text

app/models/login_request.rb validate do if errors.empty? && user.blank? errors.add :base, "Couldn't log you in as '#{email}'" end end

Slide 125

Slide 125 text

$ cucumber features/login.feature Using the default profile... .................................. 4 scenarios (4 passed) 32 steps (32 passed) 0m1.599s

Slide 126

Slide 126 text

class LoginRequest extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations def persisted? false end attr_accessor :email, :password, :remember_me def initialize(new_attributes={}) self.remember_me = true new_attributes.each do |attribute, value| if respond_to?("#{attribute}=") send("#{attribute}=", value) end end end def authenticate success = valid? logger.warn "Failed signin for #{email.inspect}" unless success success end def logger Rails.logger end def user return @user if instance_variable_defined? "@user" @user = User.authenticate(email, password) end def remember_me? remember_me.present? end validates_presence_of :email, :password validates_length_of :password, :minimum => 5, :allow_blank => true validate do if errors.empty? && user.blank? errors.add :base, "Couldn't log you in as '#{email}'" end end end

Slide 127

Slide 127 text

ActiveAttr What ActiveModel Left Out

Slide 128

Slide 128 text

Rocket Fuel For your models

Slide 129

Slide 129 text

BasicModel class Person include ActiveAttr::BasicModel end Person.model_name.plural #=> "people" person = Person.new person.valid? #=> true person.errors.full_messages #=> []

Slide 130

Slide 130 text

Before extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations def persisted? false end

Slide 131

Slide 131 text

After include ActiveAttr::BasicModel

Slide 132

Slide 132 text

Attributes class Person include ActiveAttr::Attributes attribute :first_name attribute :last_name end person = Person.new person.first_name = "Chris" person.last_name = "Griego" person.attributes #=> {"first_name"=>"Chris", "last_name"=>"Griego"}

Slide 133

Slide 133 text

Before attr_accessor :email, :password, :remember_me

Slide 134

Slide 134 text

After include ActiveAttr::Attributes attribute :email attribute :password attribute :remember_me

Slide 135

Slide 135 text

QueryAttributes class Person include ActiveAttr::QueryAttributes attribute :first_name attribute :last_name end person = Person.new person.first_name = "Chris" person.first_name? #=> true person.last_name? #=> false

Slide 136

Slide 136 text

Before def remember_me? remember_me.present? end

Slide 137

Slide 137 text

After include ActiveAttr::QueryAttributes

Slide 138

Slide 138 text

AttributeDefaults class Person include ActiveAttr::AttributeDefaults attribute :first_name, :default => "John" attribute :last_name, :default => "Doe" end person = Person.new person.first_name #=> "John" person.last_name #=> "Doe"

Slide 139

Slide 139 text

Before attribute :remember_me def initialize(new_attributes={}) self.remember_me = true new_attributes.each do |attribute, value| if respond_to?("#{attribute}=") send("#{attribute}=", value) end end end

Slide 140

Slide 140 text

After include ActiveAttr::AttributeDefaults attribute :remember_me, :default => true def initialize(new_attributes={}) new_attributes.each do |attribute, value| if respond_to?("#{attribute}=") send("#{attribute}=", value) end end end

Slide 141

Slide 141 text

MassAssignment class Person include ActiveAttr::MassAssignment attr_accessor :first_name, :last_name end person = Person.new(:first_name => "Chris") person.attributes = { :last_name => "Griego" } person.first_name #=> "Chris" person.last_name #=> "Griego"

Slide 142

Slide 142 text

Before def initialize(new_attributes={}) new_attributes.each do |attribute, value| if respond_to?("#{attribute}=") send("#{attribute}=", value) end end end

Slide 143

Slide 143 text

After include ActiveAttr::MassAssignment

Slide 144

Slide 144 text

Logger class Person include ActiveAttr::Logger end Person.logger = Logger.new(STDOUT) Person.logger? #=> true Person.logger.info "Logging an informational message" person = Person.new person.logger? #=> true person.logger = Logger.new(STDERR) person.logger.warn "Logging a warning message"

Slide 145

Slide 145 text

Before def logger Rails.logger end

Slide 146

Slide 146 text

After include ActiveAttr::Logger

Slide 147

Slide 147 text

Model class Person include ActiveAttr::Model end

Slide 148

Slide 148 text

Before include ActiveAttr::BasicModel include ActiveAttr::Attributes include ActiveAttr::QueryAttributes include ActiveAttr::AttributeDefaults include ActiveAttr::MassAssignment include ActiveAttr::Logger

Slide 149

Slide 149 text

After include ActiveAttr::Model

Slide 150

Slide 150 text

class LoginRequest include ActiveAttr::Model attribute :email attribute :password attribute :remember_me, :default => true def authenticate success = valid? logger.warn "Failed signin for #{email.inspect}" unless success success end def user return @user if instance_variable_defined? "@user" @user = User.authenticate(email, password) end validates_presence_of :email, :password validates_length_of :password, :minimum => 5, :allow_blank => true validate do if errors.empty? && user.blank? errors.add :base, "Couldn't log you in as '#{email}'" end end end

Slide 151

Slide 151 text

$ cucumber features/login.feature Using the default profile... .................................. 4 scenarios (4 passed) 32 steps (32 passed) 0m1.731s

Slide 152

Slide 152 text

$ rspec spec/models/ login_request_spec.rb .................................. Finished in 0.09344 seconds 34 examples, 0 failures

Slide 153

Slide 153 text

MassAssignmentSecurity class Person include ActiveAttr::MassAssignmentSecurity attr_accessor :first_name, :last_name attr_protected :last_name end person = Person.new(:first_name => "Chris", :last_name => "Griego") person.first_name #=> "Chris" person.last_name #=> nil

Slide 154

Slide 154 text

BlockInitialization class Person include ActiveAttr::BlockInitialization attr_accessor :first_name, :last_name end person = Person.new do |p| p.first_name = "Chris" p.last_name = "Griego" end person.first_name #=> "Chris" person.last_name #=> "Griego"

Slide 155

Slide 155 text

TypecastedAttributes class Person include ActiveAttr::TypecastedAttributes attribute :age, :type => Integer end person = Person.new person.age = "29" person.age #=> 29

Slide 156

Slide 156 text

Rails Integration gem "active_attr"

Slide 157

Slide 157 text

RSpec Integration require "active_attr/rspec" describe Person do it { should have_attribute(:first_name).with_default_value_of("John") } end

Slide 158

Slide 158 text

ActiveAttr Roadmap Multi-parameter Attributes Non-nullable Attributes Rails Generators Associations Nested Attributes

Slide 159

Slide 159 text

Image Credits The Elephant in the Room by Bit Boy http://www.flickr.com/photos/bitboy/246805948/ Binoculars by Evan Long http://www.flickr.com/photos/clover_1/1200447508/ Hiding by Susan Sermoneta http://www.flickr.com/photos/en321/33868864/ PS3 Controller by Mauro Monti http://www.flickr.com/photos/kilamdil/393406895/ Xbox Controller by Alexa Booth http://www.flickr.com/photos/xabooth/2208511222/ Nintendo Gamecube Controller by Andy Zeigert http://www.flickr.com/photos/andyz/40676384/ View and Rooftops by C.J. Peters http://www.flickr.com/photos/conlawprof/266890004/ Glittering City View by William Cho http://www.flickr.com/photos/adforce1/3876421475/ Korean Signs by Taylor Sloan http://www.flickr.com/photos/taylorsloan/4477937283/ Flint Knapper by Wessex Archaeology http://www.flickr.com/photos/wessexarchaeology/56515302/ Hat Collection by lokarta http://www.flickr.com/photos/lokar/4071667927/ Caution Men by Alyson Hurt http://www.flickr.com/photos/alykat/2930096885/ Smells by Larissa http://www.flickr.com/photos/lara68/3492153226/ Wheel Building by Andrew Schwab http://www.flickr.com/photos/aschwab/3621428436/ Socony Gas Can by BEV Norton http://www.flickr.com/photos/catchesthelight/2225634867/ Statue of the Ancient Mariner Photo by pshab http://www.flickr.com/photos/pshab/460051997/

Slide 160

Slide 160 text

He went like one that hath been stunned, And is of sense forlorn: A sadder and a wiser man, He rose the morrow morn.