rails 3 beginner to builder 2011 week 8
DESCRIPTION
This is the last of 8 presentations given at University of Texas during my Beginner to Builder Rails 3 Class. For more info and the whole series including video presentations at my blog:http://schneems.com/tagged/Rails-3-beginner-to-builder-2011TRANSCRIPT
July, 2011
Beginner to BuilderWeek8
Monday, August 8, 11
@Schneems
Rails - Week 8• Dealing with nil
• Acceptance Testing
• Partials
• Cells
• Memcache
Monday, August 8, 11
@Schneems
Dealing with Niluser = User.where(:username => "schneems").first
#<User ... >
user.image_url
>> "/schneems.png"
user = nil
user.image_url
# NameError: undefined local variable or method `user'
for #<Object:0x1001dc288>
Monday, August 8, 11
@Schneems
try to deal# use try from ActiveSupport
user = User.where(:username => "schneems").first
#<User ... >
user.try(:image_url) || "/not_found.png"
>> "/schneems.png"
user = nil
user.try(:image_url) || "/not_found.png"
>> "/not_found.png"
Monday, August 8, 11
@Schneems
|| & ||=per_page = params[:per_page]
per_page = 10 if per_page.blank?
# instead
per_page = params[:per_page]||10
# or
per_page = params[:per_page]
per_page ||= 10
Monday, August 8, 11
@Schneems
‘if’ returns result# if returns to where it was calledresult = if true "foo"else "bar"end
puts result>> "foo"
result = if false "foo"else "bar"end
puts result>> "bar"
Monday, August 8, 11
@Schneems
Acceptance Testing• Run your full application stack
• Behavior Driven Development (BDD)
• High payout for little time testing
• slow
• potentially brittle
Monday, August 8, 11
@Schneems
gem 'capybara', '0.3.7'
gem 'rspec-rails', '1.3.2'
Capybara & Rspec• Capybara
• Headless browser
• open your webpages
• click links ...
Gemfile
Monday, August 8, 11
@Schneems
# go to a path
visit root_path
visit "/"
# click buttons
click "sign in"
# fill out forms
fill_in ‘Email’, :with => "[email protected]"
Capybara & Rspec
Monday, August 8, 11
@Schneems
context "as a guest on the sign in page" do # Generate a valid user let(:user) { Factory(:user) } context ‘with valid credentials’ do #Fill in the form with the user’s credentials and submit it. before do visit root_path click ‘Sign In’ fill_in ‘Email’, :with => user.email fill_in ‘Password’, :with => ‘password’ click ‘Submit’ end
it "has a sign out link" do page.should have_xpath(‘//a’, :text => ‘Sign Out’) end
it "knows who I am" do page.should have_content(“Welcome, #{user.email}!”) end end end
Capybara & Rspechttp://codingfrontier.com/integration-testing-setup-with-rspec-and-capy
/spec/acceptance/signin_spec.rb
Monday, August 8, 11
@Schneems
DRY Views• Helpers
• Small resusable components
• content_for
• Control placement of compnents out of normal page flow
• Partials
• Large reusable components
• Cells
• Reusable components when controller type logic is needed
Monday, August 8, 11
@Schneems
ViewHelpers• Rails has build in helpers
• link_to
• image_tag
• etc.
• Write your Own
Monday, August 8, 11
@Schneems
link_to_product(product = nil)
link_to image_tag(product.image_url),
product_path(product), :class => 'product'
end
ViewHelpersapp/helpers/product_helper.rb
<%= link_to_product(@product) %>
app/views/product/index.html.erb
<a href=“...” class=”product”>
<imgr src = “...” />
</a>
Monday, August 8, 11
@Schneems
content_for & yield• Replace content using content_for
<head>
<title>
<%= yield :head %>
</title>
</head>
<p>Hello, Rails!</p>
<% content_for :head do %>
A simple page
<% end %>
<html>
<head>
<title>A simple page</title>
</head>
<body>
<p>Hello, Rails!</p>
</body>
</html>
=>
Monday, August 8, 11
@Schneems
content_for in layouts• use helpers & content_for together
in layouts
<body id="<%= id_for_body(yield(:body_id)) %>"
class="<%= classes_for_body(yield(:body_class)) %>
<%= logged_in? ? 'logged-in' : 'not-logged-in' %>">
<%= yield %>
</body>
Monday, August 8, 11
@Schneems
content_for in layouts# content_for head<%= yield :head_analytics %><%= yield :head_stylesheets %><%= yield :head_scripts %><%= yield :head %>
# content_for body<%= yield :main_title %><%= yield :sidebar_title %><%= yield :sidebar %>
# content_for footer<%= yield :footer %><%= yield :footer_libraries %><%= yield :footer_scripts %><%= yield :footer_analytics %>
Monday, August 8, 11
• Reusable chunks of view code
@Schneems
<%# renders 'spots.html.erb' with @spot %>
<%= render :partial => "spot", :object => @spot %>
<%= render "spot", :object => @spot %>
<%= render @spot %>
<%# renders 'spots.html.erb' for each spot in @spots %>
<%= render @spots %>
<%= render "spot", :collection => @spots %>
Partials
Monday, August 8, 11
@Schneems
<%# renders 'spots.html.erb' for each spot in @spots %>
<%= render @spots %>
Partials
<div>
<h3>Name:</h3>
<p><%= spot.name %></p>
<p><%= spot.description %></p>
</div>
/spots/_spots.html.erb
Monday, August 8, 11
• Locals
@Schneems
<%# renders 'spots.html.erb' for each spot in @spots %>
<%= render @spots, :locals => {:foo => ”bar”} %>
Partials
<div>
<h3>Name:</h3>
<p><%= spot.name %></p>
<p><%= spot.description %></p>
<p><%= foo %></p>
</div>
/spots/_spots.html.erb
Monday, August 8, 11
• Optional Locals
@Schneems
<%# renders 'spot.html.erb' for each spot in @spots %>
<%= render @spot2 %>
Partials
<div>
<h3>Name:</h3>
<p><%= spot.name %></p>
<p><%= spot.description %></p>
<p><%= local_assigns[:foo]||'something else' %></p>
</div>
/spots/_spot.html.erb
Monday, August 8, 11
@Schneems
gem install cells
Cells• View Components
• Look and Feel like Controllers
Monday, August 8, 11
@Schneems
<div id="header">
<%= render_cell :cart, :display, :user => @current_user %>
Cells/app/views/product/index.html
class CartCell < Cell::Rails
def display(options)
user = args[:user]
@items = user.items_in_cart
render # renders display.html.haml
end
end
<p>You have <%= @items.size %></p>
/app/cells/cart_cell.rb
/app/cells/cart.display.html.erb
Monday, August 8, 11
@Schneems
DRY Views• Helpers
• Small resusable components
• content_for
• Control placement of compnents out of normal page flow
• Partials
• Large reusable components
• Cells
• Reusable components when controller type logic is needed
Monday, August 8, 11
• Store data in a cache so it can be served quicker later
@Schneems
Caching
Monday, August 8, 11
• Super Fast
• Limited use cases
@Schneems
Page Caching
class ProductsController < ActionController
caches_page :index
def index
@products = Products.all
end
end
http://guides.rubyonrails.org/caching_with_rails.html
Monday, August 8, 11
@Schneems
Page Cachingclass ProductsController < ActionController
caches_page :index
def index
@products = Products.all
end
def create
expire_page :action => :index
end
end
http://guides.rubyonrails.org/caching_with_rails.html
Monday, August 8, 11
• Allows Authentication checks
@Schneems
Action Caching
class ProductsController < ActionController
before_filter :authenticate
caches_action :index
def index
@products = Product.all
end
def create
expire_action :action => :index
end
end
Monday, August 8, 11
• :layout => false
• allows dynamic info in the layout to render such as current user
• :if & :unless
•
@Schneems
Action Caching
caches_action :index, :layout => false
caches_action :index, :if => lambda {current_user.present?}
caches_action :index, :unless => lambda {current_user.blank?}
Monday, August 8, 11
• Cache individual parts of pages
@Schneems
Fragment Caching
<% cache('all_available_products') do %>
All available products:
<% end %>
Monday, August 8, 11
• Cache individual parts of pages
@Schneems
Fragment Caching
<% cache(:action => 'recent',
:action_suffix => 'all_products') do %>
All available products:
...
<%- end -%>
Monday, August 8, 11
• Expire fragments
@Schneems
Fragment Caching
expire_fragment(:controller => 'products',
:action => 'recent',
:action_suffix => 'all_products')
Monday, August 8, 11
@Schneems
Sweeping Cache
class ProductsController < ActionController
before_filter :authenticate
caches_action :index
cache_sweeper :product_sweeper
def index
@products = Product.all
end
end
Monday, August 8, 11
@Schneems
Sweeping Cacheclass ProductSweeper < ActionController::Caching::Sweeper
observe Product # This sweeper is going to keep an eye on the Product model
# If our sweeper detects that a Product was created call this def after_create(product)
expire_cache_for(product)
end
private
def expire_cache_for(product)
# Expire the index page now that we added a new product
expire_page(:controller => 'products', :action => 'index')
expire_fragment('all_available_products')
end
end
Monday, August 8, 11
• Set the Cache store in your environment
@Schneems
Cache Stores
ActionController::Base.cache_store = :memory_store
ActionController::Base.cache_store = :drb_store,
"druby://localhost:9192"
ActionController::Base.cache_store = :mem_cache_store,
"localhost"
Monday, August 8, 11
• Simple Key/Value storage
@Schneems
Cache Stores
Rails.cache.read("city") # => nil
Rails.cache.write("city", "Duckburgh")
Rails.cache.read("city") # => "Duckburgh"
Monday, August 8, 11
• fetch
@Schneems
Cache Stores
Rails.cache.read("city") # => nil
Rails.cache.fetch("city") do
"Duckburgh"
end
# => "Duckburgh"
Rails.cache.read("city") # => "Duckburgh"
Monday, August 8, 11
• Expires_in
@Schneems
Cache Stores
Rails.cache.write("city", "Duckburgh", :expires_in =>
1.minute)
Rails.cache.read("city") # => "Duckburgh"
sleep 120
Rails.cache.read("city") # => nil
Monday, August 8, 11
• Cache your rails objects
• Memcache Gem
• Key Value Store (NOSQL)
• Use to cache Expensive Queries
@Schneems
require 'memcached'
$cache = Memcached.new("localhost:11211")
$cache.set("foo", "bar")
$cache.get("foo")
=> "bar"
Memcache
Monday, August 8, 11
@Schneems
# store strings
$cache.set("key", "value")
$cache.get("key")
=> "bar"
# store any ruby object
$cache.set("foo", [1,2,3,4,5])
$cache.get("foo")
=> [1,2,3,4,5]
# srsly any ruby object
$cache.set("foo", User.last)
$cache.get("foo")
=> #< User ... >
Memcache
Monday, August 8, 11
@Schneems
ActionController::Base.cache_store = :mem_cache_store,
"localhost"
Memcache
Monday, August 8, 11
• Cache frequent queries
@Schneems
class User < ActiveRecord::Base
def self.fetch(username)
Rails.cache.fetch("user:#{username}") do
User.where(:username => "#{username}").first
end
end
end
=> User.fetch("schneems") #=> #<User id: 263595 ... >
Memcache
Monday, August 8, 11
• Cache expensive queries
@Schneems
class User < ActiveRecord::Base
def fetch_friends
Rails.cache.fetch("users:friends:#{self.id}") do
friends = Friendships.where(:user_1_id => self.id)
end
end
end
=> User.fetch("schneems").fetch_friend_ids
Memcache
Monday, August 8, 11
• Blazing Fast
@Schneems
# Database
Benchmark.measure do
User.where(:username => "schneems").first
end.real
=> 0.09 s
# Memcache
Benchmark.measure do
User.fetch("schneems")
end.real
=> 0.0009 s
Memcache
Monday, August 8, 11
• Key Value Store
• Holds ruby objects
• Extremely fast
• volatile
• available on Heroku
@Schneems
>> heroku addons:add memcache
Memcache
Monday, August 8, 11
• Use Keytar to generate Keys
• Use those keys in Memcache
@Schneems
class User < ActiveRecord::Base
include Keytar
define_keys :username, :friends
end
User.username_key("schneems")
=> "user:username:schneems"
user = Rails.cache.fetch(User.username_key("schneems"))
=> # < User id:33 ... >
user.friends_key
=> "users:friends:33"
friends = Rails.cache.fetch(user.friends_key)
=> #[#< User ... >, #< User ... >, ... ]
Bonus - Keytar
Monday, August 8, 11
@Schneems
Questions?
http://guides.rubyonrails.orghttp://stackoverflow.com
http://peepcode.com
Monday, August 8, 11