SlideShare a Scribd company logo
1 of 20
Download to read offline
authlogic
class UserSession < Authlogic::Session::Base
end


class ApplicationController < ActionController::Base
  
  private

  def current_user_session
    UserSession.find
  end
    
  def current_user
    current_user_session && current_user_session.record
  end




                                                 binarylogic
bcrypt-ruby
require 'bcrypt'

class User < ActiveRecord::Base
  # users.password_hash in the database is a :string
  include BCrypt

  def password
    @password ||= Password.new(password_hash)
  end

  def password=(new_password)
    @password = Password.create(new_password)
    self.password_hash = @password
  end
end




                                                       codahale
capybara
describe "the signup process", :type => :request do
  before :each do
    User.make(:email => 'user@ex.com', :password => 'caplin')
  end

  it "signs me in" do
    within("#session") do
      fill_in 'Login', :with => 'user@example.com'
      fill_in 'Password', :with => 'password'
    end
    click_link 'Sign in'
  end
end




                                                       jnicklas
dim

GameContainer = Dim::Container.new

GameContainer.register(:environment) { ENV['EXP_ENV'] }

GameContainer.register(:transmitter) do |c|
  Transmitter.new(c.environment)
end




                     jimweirich (gem-ified by me)
factory_girl
Factory.define :simple_map, :parent => :map do |m|
  m.name "Simple Map"
  m.size 5

  m.after_create do |map|
    map.with_grid do |grid|
      grid.add_feature(Terrain::Dirt,1,1,3,3)
      grid.add_feature(MapObjects::BlueCrystal,1,1)
      grid.add_feature(Landmarks::Mountain,4,4)
    end
  end
end


context "saving and restoring" do
  it "preserves all data types in the grid" do
    map = Factory(:simple_map)
    map.feature_at(1,1).terrain.name.should == Terrain::Dirt
  end
end


                                                 thoughtbot
foreigner

add_foreign_key :maps, :terrain, :column => :default_terrain_id




                                            matthuhiggins
formtastic
%h1 Sign Up

= semantic_form_for @user, :url => signup_path do |form|
  = form.semantic_errors

  = form.inputs do
    = form.input :email, input_html: { autofocus: true }
    = form.input :identifier, :label => "In-Game Identifier"
    = form.input :password

  = form.buttons do
    = form.commit_button "Signup"




                                                justinfrench
girl_friday
QUEUE = GirlFriday::WorkQueue.new(:user_email, :size => 3) do |m|
  UserMailer.registration_email(m).deliver
end


# while handling a web request
QUEUE.push(:email => @user.email, :name => @user.name)




                                                    mperham
grb
=> create new branch `branch`
  $ grb new [branch]

=> track branch
  $ grb track `branch`

=> add a remote repo
  $ grb remote_add `name` `repo path`

=> remove a remote repo
  $ grb remote_rm `name`




                                        jinzhu
guard (guard-*)
guard 'bundler' do
  watch('Gemfile')
end

guard 'rspec' do
  watch(%r{^spec/.+_spec.rb})
  watch(%r{^lib/(.+).rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }

  watch('spec/spec_helper.rb') { "spec" }
end

guard 'coffeescript', :output => 'public/js/compiled' do
  watch(%r{^app/coffeescripts/.+.coffee})
end




                                                   thibaudgg
jammit
embed_assets: on
gzip_assets: on
javascript_compressor: yui

javascripts:
  workspace:
    - public/javascripts/vendor/jquery.js
    - public/javascripts/lib/*.js
    - public/javascripts/views/**/*.js
    - app/views/workspace/*.jst

stylesheets:
  common:
    - public/stylesheets/reset.css
    - public/stylesheets/widgets/*.css
  workspace:
    - public/stylesheets/pages/workspace.css
  empty:
    - public/stylesheets/pages/empty.css




                                            documentcloud
omniauth

require 'oa-oauth'
use OmniAuth::Strategies::Twitter, 'KEY', 'SECRET'

# redirect user to /auth/twitter
# twitter redirects user to /auth/twitter/callback

get '/auth/twitter/callback' do
  auth_hash = request.env['omniauth.auth']
end




                                                     intridea
ostruct*
require 'ostruct'

record = OpenStruct.new
record.name    = "John Smith"
record.age     = 70
record.pension = 300

puts record.name      # -> "John Smith"
puts record.address   # -> nil




                                          *part of stdlib
perftools.rb




               tmm1
pg_sexy_constraints

class AddConstraintsToBooks < ActiveRecord::Migration
  def self.up
    constrain :books do |t|
      t.title :not_blank => true, :alphanumeric => true
      t.isbn :unique => true, :blacklist => %w(badbook1)
    end
  end
end




                                                           maxim
pusher
# server
Pusher['a_channel'].trigger!('an_event', {:some => 'data'})

# client-side JS

var pusher = new Pusher('API_KEY');

pusher.subscribe('a_channel');
pusher.bind('an_event',
   function(data) {
     console.info(data.some); // “data”
   }
);




                                                        pusher
right_aws
query = ["['cat'=?] union ['dog'=?]", "clew", "Jon's boot"]
sdb.query('family', query)

queue = RightAws::SqsGen2::Queue.create(sqs, 'my_cool_queue')
msg = queue.receive
msg.visibility = 0
queue.clear(true)
queue.send_message('example')
msg2 = queue.pop




                                                   rightscale
spork
Guard is now watching at '/Users/subelsky/code/exp-world'
Starting Spork for RSpec

Spork server for RSpec successfully started
Guard::RSpec is running, with RSpec 2!
Running all specs
Using RSpec
Loading Spork.prefork block...
........................................................

Finished in 2 seconds
74 examples, 0 failures




                                                            URL
therubyracer

require 'v8'

cxt = V8::Context.new
cxt.eval('parseInt("50",10)') # => 50

cxt["say"] = lambda { |word, times| word * times }
cxt.eval("say('Hello', 3)") # => HelloHelloHello




                                                     cowboyd
timecop

joe = User.find(1)
joe.purchase_home()
assert !joe.mortgage_due?

# move ahead a month and assert that the mortgage is due
Timecop.freeze(Date.today + 30) do
  assert joe.mortgage_due?
end




                                                     jtrupiano

More Related Content

More from Mike Subelsky

It's Not Always Sunny in the Clouds
It's Not Always Sunny in the CloudsIt's Not Always Sunny in the Clouds
It's Not Always Sunny in the CloudsMike Subelsky
 
Introduction to SproutCore at JSConf
Introduction to SproutCore at JSConfIntroduction to SproutCore at JSConf
Introduction to SproutCore at JSConfMike Subelsky
 
Scaling Rails Applications In The Cloud
Scaling Rails Applications In The CloudScaling Rails Applications In The Cloud
Scaling Rails Applications In The CloudMike Subelsky
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsMike Subelsky
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsMike Subelsky
 

More from Mike Subelsky (6)

Ruby For Startups
Ruby For StartupsRuby For Startups
Ruby For Startups
 
It's Not Always Sunny in the Clouds
It's Not Always Sunny in the CloudsIt's Not Always Sunny in the Clouds
It's Not Always Sunny in the Clouds
 
Introduction to SproutCore at JSConf
Introduction to SproutCore at JSConfIntroduction to SproutCore at JSConf
Introduction to SproutCore at JSConf
 
Scaling Rails Applications In The Cloud
Scaling Rails Applications In The CloudScaling Rails Applications In The Cloud
Scaling Rails Applications In The Cloud
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 

Recently uploaded

Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...gurkirankumar98700
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilV3cube
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsRoshan Dwivedi
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 

Recently uploaded (20)

Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 

20 Favorite Ruby Gems (Ignite RailsConf)

  • 1. authlogic class UserSession < Authlogic::Session::Base end class ApplicationController < ActionController::Base      private   def current_user_session     UserSession.find   end        def current_user     current_user_session && current_user_session.record   end binarylogic
  • 2. bcrypt-ruby require 'bcrypt' class User < ActiveRecord::Base # users.password_hash in the database is a :string include BCrypt def password @password ||= Password.new(password_hash) end def password=(new_password) @password = Password.create(new_password) self.password_hash = @password end end codahale
  • 3. capybara describe "the signup process", :type => :request do before :each do User.make(:email => 'user@ex.com', :password => 'caplin') end it "signs me in" do within("#session") do fill_in 'Login', :with => 'user@example.com' fill_in 'Password', :with => 'password' end click_link 'Sign in' end end jnicklas
  • 4. dim GameContainer = Dim::Container.new GameContainer.register(:environment) { ENV['EXP_ENV'] } GameContainer.register(:transmitter) do |c| Transmitter.new(c.environment) end jimweirich (gem-ified by me)
  • 5. factory_girl Factory.define :simple_map, :parent => :map do |m| m.name "Simple Map" m.size 5 m.after_create do |map| map.with_grid do |grid| grid.add_feature(Terrain::Dirt,1,1,3,3) grid.add_feature(MapObjects::BlueCrystal,1,1) grid.add_feature(Landmarks::Mountain,4,4) end end end context "saving and restoring" do it "preserves all data types in the grid" do map = Factory(:simple_map) map.feature_at(1,1).terrain.name.should == Terrain::Dirt end end thoughtbot
  • 6. foreigner add_foreign_key :maps, :terrain, :column => :default_terrain_id matthuhiggins
  • 7. formtastic %h1 Sign Up = semantic_form_for @user, :url => signup_path do |form| = form.semantic_errors = form.inputs do = form.input :email, input_html: { autofocus: true } = form.input :identifier, :label => "In-Game Identifier" = form.input :password = form.buttons do = form.commit_button "Signup" justinfrench
  • 8. girl_friday QUEUE = GirlFriday::WorkQueue.new(:user_email, :size => 3) do |m| UserMailer.registration_email(m).deliver end # while handling a web request QUEUE.push(:email => @user.email, :name => @user.name) mperham
  • 9. grb => create new branch `branch` $ grb new [branch] => track branch $ grb track `branch` => add a remote repo $ grb remote_add `name` `repo path` => remove a remote repo $ grb remote_rm `name` jinzhu
  • 10. guard (guard-*) guard 'bundler' do watch('Gemfile') end guard 'rspec' do watch(%r{^spec/.+_spec.rb}) watch(%r{^lib/(.+).rb}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec" } end guard 'coffeescript', :output => 'public/js/compiled' do watch(%r{^app/coffeescripts/.+.coffee}) end thibaudgg
  • 11. jammit embed_assets: on gzip_assets: on javascript_compressor: yui javascripts: workspace: - public/javascripts/vendor/jquery.js - public/javascripts/lib/*.js - public/javascripts/views/**/*.js - app/views/workspace/*.jst stylesheets: common: - public/stylesheets/reset.css - public/stylesheets/widgets/*.css workspace: - public/stylesheets/pages/workspace.css empty: - public/stylesheets/pages/empty.css documentcloud
  • 12. omniauth require 'oa-oauth' use OmniAuth::Strategies::Twitter, 'KEY', 'SECRET' # redirect user to /auth/twitter # twitter redirects user to /auth/twitter/callback get '/auth/twitter/callback' do auth_hash = request.env['omniauth.auth'] end intridea
  • 13. ostruct* require 'ostruct' record = OpenStruct.new record.name = "John Smith" record.age = 70 record.pension = 300 puts record.name # -> "John Smith" puts record.address # -> nil *part of stdlib
  • 14. perftools.rb tmm1
  • 15. pg_sexy_constraints class AddConstraintsToBooks < ActiveRecord::Migration def self.up constrain :books do |t| t.title :not_blank => true, :alphanumeric => true t.isbn :unique => true, :blacklist => %w(badbook1) end end end maxim
  • 16. pusher # server Pusher['a_channel'].trigger!('an_event', {:some => 'data'}) # client-side JS var pusher = new Pusher('API_KEY'); pusher.subscribe('a_channel'); pusher.bind('an_event', function(data) { console.info(data.some); // “data” } ); pusher
  • 17. right_aws query = ["['cat'=?] union ['dog'=?]", "clew", "Jon's boot"] sdb.query('family', query) queue = RightAws::SqsGen2::Queue.create(sqs, 'my_cool_queue') msg = queue.receive msg.visibility = 0 queue.clear(true) queue.send_message('example') msg2 = queue.pop rightscale
  • 18. spork Guard is now watching at '/Users/subelsky/code/exp-world' Starting Spork for RSpec Spork server for RSpec successfully started Guard::RSpec is running, with RSpec 2! Running all specs Using RSpec Loading Spork.prefork block... ........................................................ Finished in 2 seconds 74 examples, 0 failures URL
  • 19. therubyracer require 'v8' cxt = V8::Context.new cxt.eval('parseInt("50",10)') # => 50 cxt["say"] = lambda { |word, times| word * times } cxt.eval("say('Hello', 3)") # => HelloHelloHello cowboyd
  • 20. timecop joe = User.find(1) joe.purchase_home() assert !joe.mortgage_due? # move ahead a month and assert that the mortgage is due Timecop.freeze(Date.today + 30) do assert joe.mortgage_due? end jtrupiano