Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

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. GUI Architectures Model View Controller Model View Adapter Model View

    Presenter Model View ViewModel Hierarchical Model View Controller
  2. “Model objects are the things in the system that you

    are trying to model.” David Heinemeier Hansson, 2004
  3. app/views/sessions/new.html.erb <% flash.each do |name, message| %> <%= content_tag(:p, message)

    %> <% end %> <h1>Log In</h1> <%= form_tag session_path do %> <div class="field"> <%= label_tag 'email' %><br /> <%= text_field_tag 'email', @email %> </div> <div class="field"> <%= label_tag 'password' %><br/> <%= password_field_tag 'password', nil %><br /> <%= link_to 'Forgot Password?', password_help_path %> </div> <div class="field"> <%= check_box_tag 'remember_me', '1', @remember_me.nil? ? true : @remember_me %> <%= label_tag 'remember_me' %> </div> <div class="actions"> <%= submit_tag 'Log in' %> </div> <% end %>
  4. app/views/sessions/new.html.erb <% flash.each do |name, message| %> <%= content_tag(:p, message)

    %> <% end %> <h1>Log In</h1> <%= form_tag session_path do %> <%# … %> <% end %>
  5. 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
  6. 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
  7. 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
  8. 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}"
  9. 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
  10. 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"
  11. 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
  12. 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"
  13. 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"
  14. A New View On Life <h1>Log In</h1> <%= form_for @login_request,

    :url => session_path do |f| %> <% @login_request.errors.full_messages.each do |message| %> <%= content_tag(:p, message) %> <% end %> <div class="field"> <%= f.label :email %><br /> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :password %><br /> <%= f.password_field :password %><br /> <%= link_to 'Forgot Password?', password_help_path %> </div> <div class="field"> <%= f.check_box :remember_me %> <%= f.label :remember_me %> </div> <div class="actions"> <%= f.submit "Log in" %> </div> <% end %>
  15. Before <% flash.each do |name, message| %> <%= content_tag(:p, message)

    %> <% end %> <h1>Log In</h1> <%= form_tag session_path do %> <%# … %> <% end %>
  16. After <h1>Log In</h1> <%= form_for @login_request, :url => session_path do

    |f| %> <% @login_request.errors.full_messages.each do |message| %> <%= content_tag(:p, message) %> <% end %> <%# … %> <% end %>
  17. Before <div class="field"> <%= label_tag 'password' %><br/> <%= password_field_tag 'password',

    nil %><br /> <%= link_to 'Forgot Password?', password_help_path %> </div>
  18. After <div class="field"> <%= f.label :password %><br /> <%= f.password_field

    :password %><br /> <%= link_to 'Forgot Password?', password_help_path %> </div>
  19. 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
  20. 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
  21. 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
  22. 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}"
  23. $ cucumber features/login.feature Using the default profile... .F.F------------------------------ (::) failed

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

    steps (::) undefined method `model_name' for LoginRequest:Class (ActionView::Template::Error
  25. When does a model need to be an ActiveModel? Railties

    ActiveSupport ActiveRecord ActiveResource ActiveModel ActionMailer
  26. When does ActionPack need an ActiveModel? Polymorphic Routes (url_for) Partial

    Rendering (render) Record Identification (dom_class, dom_id) Form Building (form_for)
  27. $ 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. <false> is not true.
  28. $ 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. <false> is not true.
  29. $ 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. <false> is not true.
  30. $ 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?. <false> is not true.
  31. $ 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?. <false> is not true.
  32. $ cucumber features/login.feature Using the default profile... .F.F------------------------------ (::) failed

    steps (::) undefined method `email' for #<LoginRequest:0x104c55fd0> (ActionView::Template::Error)
  33. $ 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'
  34. $ 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'
  35. $ cucumber features/login.feature Using the default profile... .......F-...F-----....F---....F--- (::) failed

    steps (::) undefined method `authenticate' for #<LoginRequest:0x1178886b0> (NoMethodError) ./app/controllers/ sessions_controller.rb:12:in `create'
  36. 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
  37. $ cucumber features/login.feature Using the default profile... .......F-...F-----....F---....F--- (::) failed

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

    steps (::) undefined method `remember_me?' for #<LoginRequest:0x111efadf0> (NoMethodError) ./app/controllers/ sessions_controller.rb:14:in `create'
  39. 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
  40. BasicModel class Person include ActiveAttr::BasicModel end Person.model_name.plural #=> "people" person

    = Person.new person.valid? #=> true person.errors.full_messages #=> []
  41. 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"}
  42. 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
  43. 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"
  44. 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
  45. 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
  46. 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"
  47. 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"
  48. 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
  49. 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
  50. 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"
  51. RSpec Integration require "active_attr/rspec" describe Person do it { should

    have_attribute(:first_name).with_default_value_of("John") } end
  52. 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/
  53. He went like one that hath been stunned, And is

    of sense forlorn: A sadder and a wiser man, He rose the morrow morn.