$30 off During Our Annual Pro Sale. View Details »

Models Models Every Where

Chris Griego
February 07, 2012

Models Models Every Where

Models form the backbone of our applications, but what are they and where to they come from? More importantly, do you have enough models or is your application missing some? Chris will show you how to spot missing models as well as create models with all of the goodies you’ve come to expect from Ruby on Rails. Along the way you’ll be introduced to ActiveAttr which makes it easy to construct models.

Chris Griego

February 07, 2012
Tweet

Other Decks in Programming

Transcript

  1. Models Models
    Every Where
    The Rime of the ActiveModel

    View Slide

  2. SCALE MODEL?

    View Slide

  3. MODEL HOME?

    View Slide

  4. TYRA BANKS?

    View Slide

  5. What are models?

    View Slide

  6. app/models

    View Slide

  7. ActiveRecord

    View Slide

  8. Rails + Merb = 3.0

    View Slide

  9. The Great Decoupling
    ActiveRecord
    ActiveResource
    Sequel
    DataMapper
    Mongoid
    MongoMapper

    View Slide

  10. Why app/models?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. But what are models?

    View Slide

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

    View Slide

  17. MODELS REPRESENT KNOWLEDGE
    MODELS = KNOWLEDGE, KNOWLEDGE = POWER, MODELS = POWER

    View Slide

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

    View Slide

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

    View Slide

  20. SPOTTING MISSING MODELS

    View Slide

  21. WHERE DO MODELS HIDE?
    UNDER YOUR NOSE

    View Slide

  22. MODELS HIDE IN CONTROLLERS
    FAT CONTROLLERS?

    View Slide

  23. MODELS HIDE IN VIEWS
    LOGIC LADEN VIEWS?

    View Slide

  24. MODELS HIDE IN OTHER MODELS
    MODELS WITH TOO MUCH RESPONSIBILITY

    View Slide

  25. MODELS HIDE BITS OF THEMSELVES ALL OVER
    SPREAD IT AROUND

    View Slide

  26. SIGNS TO WATCH FOR

    View Slide

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

    View Slide

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

    View Slide

  29. LARGE CLASS
    MULTIPLE RESPONSIBILITIES

    View Slide

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

    View Slide

  31. Example
    LoginRequest

    View Slide

  32. 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 %>

    View Slide

  33. app/views/sessions/new.html.erb
    <% flash.each do |name, message| %>
    <%= content_tag(:p, message) %>
    <% end %>
    Log In
    <%= form_tag session_path do %>
    <%# … %>
    <% end %>

    View Slide

  34. app/views/sessions/new.html.erb

    <%= label_tag 'email' %>

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

    View Slide

  35. app/views/sessions/new.html.erb

    <%= label_tag 'password' %>

    <%= password_field_tag 'password', nil
    %>

    <%= link_to 'Forgot Password?',
    password_help_path %>

    View Slide

  36. app/views/sessions/new.html.erb

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

    View Slide

  37. app/views/sessions/new.html.erb

    <%= submit_tag 'Log in' %>

    View Slide

  38. 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

    View Slide

  39. app/controllers/sessions_controller.rb
    def new
    end

    View Slide

  40. app/controllers/sessions_controller.rb
    def create
    logout_keeping_session!

    View Slide

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

    View Slide

  42. 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

    View Slide

  43. 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

    View Slide

  44. 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}"

    View Slide

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

    View Slide

  46. CODE SMELLS

    View Slide

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

    View Slide

  48. SCATTERED KNOWLEDGE
    KNOWLEDGE IN CONTROLLERS AND VIEWS

    View Slide

  49. Refactor
    with Caution
    Refactor with Tests

    View Slide

  50. 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

    View Slide

  51. 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"

    View Slide

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

    View Slide

  53. 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

    View Slide

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

    View Slide

  55. 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"

    View Slide

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

    View Slide

  57. 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"

    View Slide

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

    View Slide

  59. View Slide

  60. 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 %>

    View Slide

  61. Before
    <% flash.each do |name, message| %>
    <%= content_tag(:p, message) %>
    <% end %>
    Log In
    <%= form_tag session_path do %>
    <%# … %>
    <% end %>

    View Slide

  62. 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 %>

    View Slide

  63. Before

    <%= label_tag 'email' %>

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

    View Slide

  64. After

    <%= f.label :email %>

    <%= f.text_field :email %>

    View Slide

  65. Before

    <%= label_tag 'password' %>

    <%= password_field_tag 'password', nil
    %>

    <%= link_to 'Forgot Password?',
    password_help_path %>

    View Slide

  66. After

    <%= f.label :password %>

    <%= f.password_field :password %>

    <%= link_to 'Forgot Password?',
    password_help_path %>

    View Slide

  67. Before

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

    View Slide

  68. After

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

    View Slide

  69. Before

    <%= submit_tag 'Log in' %>

    View Slide

  70. After

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

    View Slide

  71. 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

    View Slide

  72. Before
    def new
    end

    View Slide

  73. After
    def new
    @login_request = LoginRequest.new
    end

    View Slide

  74. Before & After
    def create
    logout_keeping_session!

    View Slide

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

    View Slide

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

    View Slide

  77. 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

    View Slide

  78. 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

    View Slide

  79. 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

    View Slide

  80. 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}"

    View Slide

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

    View Slide

  82. After
    render :new

    View Slide

  83. NOW WE LET THE TESTS TAKE THE WHEEL
    TEST DRIVEN DEVELOPMENT

    View Slide

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

    View Slide

  85. app/models/login_request.rb
    class LoginRequest
    end

    View Slide

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

    View Slide

  87. The ActiveModel API
    #to_model

    View Slide

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

    View Slide

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

    View Slide

  90. ActiveModel API
    is for ActionPack

    View Slide

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

    View Slide

  92. ActiveModel::Lint::Tests

    View Slide

  93. $ 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.

    View Slide

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

    View Slide

  95. $ 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.

    View Slide

  96. The ActiveModel Library
    gem install activemodel

    View Slide

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

    View Slide

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

    View Slide

  99. $ 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.

    View Slide

  100. ActiveModel::Conversion
    #to_model
    #to_key
    #to_param

    View Slide

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

    View Slide

  102. $ 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.

    View Slide

  103. 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

    View Slide

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

    View Slide

  105. $ 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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  110. $ 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'

    View Slide

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

    View Slide

  112. $ 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'

    View Slide

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

    View Slide

  114. 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

    View Slide

  115. $ 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'

    View Slide

  116. 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

    View Slide

  117. $ 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'

    View Slide

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

    View Slide

  119. $ 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'

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  126. 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

    View Slide

  127. ActiveAttr
    What ActiveModel Left Out

    View Slide

  128. Rocket Fuel
    For your models

    View Slide

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

    View Slide

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

    View Slide

  131. After
    include ActiveAttr::BasicModel

    View Slide

  132. 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"}

    View Slide

  133. Before
    attr_accessor :email, :password,
    :remember_me

    View Slide

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

    View Slide

  135. 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

    View Slide

  136. Before
    def remember_me?
    remember_me.present?
    end

    View Slide

  137. After
    include ActiveAttr::QueryAttributes

    View Slide

  138. 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"

    View Slide

  139. 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

    View Slide

  140. 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

    View Slide

  141. 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"

    View Slide

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

    View Slide

  143. After
    include ActiveAttr::MassAssignment

    View Slide

  144. 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"

    View Slide

  145. Before
    def logger
    Rails.logger
    end

    View Slide

  146. After
    include ActiveAttr::Logger

    View Slide

  147. Model
    class Person
    include ActiveAttr::Model
    end

    View Slide

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

    View Slide

  149. After
    include ActiveAttr::Model

    View Slide

  150. 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

    View Slide

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

    View Slide

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

    View Slide

  153. 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

    View Slide

  154. 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"

    View Slide

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

    View Slide

  156. Rails Integration
    gem "active_attr"

    View Slide

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

    View Slide

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

    View Slide

  159. 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/

    View Slide

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

    View Slide