0
1
controllers In space
2
level 1
sad Code
Happy Code
controllers In space
3
LEVEL 1 controllers In Space
Example
4
elsif .retweets.where(:user_id =>tweet
tweet.user == current_userif
current_user.id).present?
LEVEL 1 controllers In Space...
LEVEL 1 controllers In Space
elsif .retweets.where(:user_id =>self
self.user == retweeter
FAT MODEL, SKINNY CONTROLLER
/ap...
@trending = Topic.find(
:all,
:conditions => ["started_trending > ?", 1.day.ago],
:order => 'mentions desc',
:limit => 5
)...
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
def index
@tweets = Tweet.
...
end
where( ...
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
...
end
( ).
( )
'created_at desc'order
li...
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
...
end
(
( )
'created_at desc'order
limit...
default_
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
...
end
(
( )
'created_at desc'
l...
limit(5)
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
...
end
/app/models/topic.rb
...
...
limit(5)
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
...
end
/app/models/topic.rb
...
...
||num
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
...
end
/app/models/topic.rb
...
end...
|num
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
...
end
/app/models/topic.rb
...
end
...
default_
LEVEL 1 controllers In Space
Scope it out
('created_at desc'
/app/models/tweet.rb
...
end
)scope
class Tweet < Ac...
LEVEL 1 controllers In Space
Scope it out
/app/controllers/tweets_controller.rb
t = Tweet.new
t.status = "RT #{@tweet.user...
LEVEL 1 controllers In Space
fantastic filters
/app/controllers/tweets_controller.rb
class TweetsController < ApplicationC...
LEVEL 1 controllers In Space
/app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
end
def ...
LEVEL 1 controllers In Space
@tweet = Tweet.find(params[:id])
/app/controllers/tweets_controller.rb
class TweetsController...
@tweet =
LEVEL 1 controllers In Space
class TweetsController < ApplicationController
end
def edit
def update
def destroy
e...
LEVEL 1 controllers In Space
What should they be used for?
authorization
fantastic filters
Logging
wizards
22
LEVEL 1 controllers In Space
fantastic filters
/app/controllers/tweets_controller.rb
class TweetsController < ApplicationC...
level 2
controller command
24
LEVEL 1 CONTROLLING YOUR CONTROLLERS
Example
25
/app/models/user.rb
class User < ActiveRecord::Base
has_one :account_setting, :dependent => :destroy
end
<div class="field...
Nested attributes
/app/controllers/users_controller.rb
@account_setting.save
@user = User.new(params[:user])
@account_sett...
Nested attributes
/app/controllers/users_controller.rb
@user = User.new(params[:user])
class UsersController < Application...
/app/models/user.rb
Nested attributes
/app/views/users/edit.html.erb
end
class UsersController < ApplicationController
def...
Models without the database
LEVEL 2 controller command
30
/app/views/contact_us/new.html.erb
Models without the database
<h1>Contact Us</h1>
<%= form_for :contact, :url => send_ema...
/app/controllers/contact_us_controller.rb
Models without the database
class ContactUsController < ApplicationController
de...
@contact_form.valid?!
Models without the database
class ContactUsController < ApplicationController
def new
end
def send_e...
@contact_form.valid?
Models without the database
class ContactUsController < ApplicationController
def new
end
def send_em...
@contact_form.valid?
Models without the database
class ContactUsController < ApplicationController
def new
end
def send_em...
render
@contact_form.valid?
Models without the database
class ContactUsController < ApplicationController
def new
end
def ...
/app/views/contact_us/new.html.erb
Models without the database
<%= form_for , :url => send_email_path do |f| %>
<h1>Contac...
/app/models/contact_form.rb
Models without the database
class ContactForm
attr_accessor :name, :email, :body
end
validates...
Models without the database
LEVEL 2 controller command
39
really Rest
class UsersController
def
< ApplicationController
subscribe_mailing_list
current_user.subscribe(params[:id])
r...
/app/controllers/subscrip5ons_controller.rb
really Rest
class SubscriptionsController
def
< ApplicationController
create
c...
really Rest
Use your best judgement
More than 2 levels is bad
/users/1/posts/2/comments/3
Not using REST is okay
/config/ro...
Enter the Presenters
LEVEL 2 controller command
43
Enter the Presenters
/app/controllers/tweets_controller.rb
@followers_tweets = current_user.followers_tweets.limit(20)
@re...
Enter the Presenters
/app/controllers/tweets_controller.rb
def index
end
@presenter = Tweets::IndexPresenter.new(current_u...
Enter the Presenters
/app/presenters/tweets/index_presenter.rb
def initialize(user)
@user = user
end
class Tweets::IndexPr...
Enter the Presenters
/app/presenters/tweets/index_presenter.rb
def initialize(user)
@user = user
end
class Tweets::IndexPr...
Enter the Presenters
/app/presenters/tweets/index_presenter.rb
def initialize(user)
@user = user
end
class Tweets::IndexPr...
Enter the Presenters
/app/presenters/tweets/index_presenter.rb
def initialize(user)
@user = user
end
class Tweets::IndexPr...
Enter the Presenters
/app/presenters/tweets/index_presenter.rb
def initialize(user)
@user = user
end
class Tweets::IndexPr...
Memoization
one is better than the other
value is not stored
if false or nil is returned
||=
extend ActiveSupport::Memoiza...
reject sql injection
User.where("name = #{params[:name]}")
User.where("name = ?", params[:name])
Tweet.where("created_at >...
/app/controllers/users_controller.rb
Rails 3 responder syntax
do |format|
format.html # show.html.erb
format.xml { render ...
/app/controllers/users_controller.rb
Rails 3 responder syntax
respond_to
def index
@users = User.all
end
...
end
def show
...
level 3
Model Mayhem
55
LEVEL 3 Model mayhem
Loving your indices
current_user.tweets
class AddIndexesToTables < ActiveRecord::Migration
def self.u...
LEVEL 3 Model mayhem
current_user.tweets.order('created_at desc').limit(10)
Topic.where("started_trending > ?", 1.day.ago)...
Use your best judgement
More indices, more time it takes to reindex
If a 2 second query runs 5 times a week,
LEVEL 3 Model...
LEVEL 3 Model mayhem
59
LEVEL 3 Model mayhem
60
LEVEL 3 Model mayhem
$ curl -d "user[login]=hacked&user[is_admin]=true&user[password]
=password&user[password_confirmation...
protecting your attributes
/app/models/user.rb
class User < ActiveRecord::Base
attr_protected :is_admin
end
/app/models/us...
default values
/app/models/account_se7ng.rb
before_create :set_default_timezone
def set_default_timezone
self.time_zone = ...
default values
/app/models/account_se7ng.rb
end
class AccountSetting < ActiveRecord::Base
belongs_to :user
class AddDefaul...
Proper use of callbacks
/app/models/topic.rb
LEVEL 3 Model mayhem
Time.now + (60 * 60 * 24 * 7)self.finish_trending =
end
...
Proper use of callbacks
/app/models/topic.rb
LEVEL 3 Model mayhem
1.weekself.finish_trending =
end
end
.from_now
class Top...
Proper use of callbacks
/app/models/topic.rb
before_create :set_trend_ending
private
def set_trend_ending
LEVEL 3 Model ma...
Proper use of callbacks
LEVEL 3 Model mayhem
Crea;ng	
  an	
  object
before_validation
after_validation
before_save
after_...
Rails date helpers
LEVEL 3 Model mayhem
Date	
  Helpers
1.minute
2.hour
3.days
4.week
5.months
6.year
Modifiers More	
  Mod...
Proper use of callbacks
LEVEL 3 Model mayhem
70
Proper use of callbacks
LEVEL 3 Model mayhem
/app/models/following.rb
class Following < ActiveRecord::Base
after_create :s...
Proper use of callbacks
LEVEL 3 Model mayhem
/app/models/following.rb
class Following < ActiveRecord::Base
after_create :
...
Proper use of callbacks
LEVEL 3 Model mayhem
/app/models/following.rb
class Following < ActiveRecord::Base
after_create :q...
improved validation
LEVEL 3 Model mayhem
.errors.add(:name, 'is inappropriate')
selfunless ContentModerator.is_suitable?( ...
improved validation
LEVEL 3 Model mayhem
.errors.add(:name, 'is inappropriate')
/app/models/topic.rb
end
end
class Topic <...
improved validation
LEVEL 3 Model mayhem
/app/models/topic.rb
end
end
class Topic < ActiveRecord::Base
validates :name, :a...
Sowing the Seeds
LEVEL 3 Model mayhem
/db/migrate/20110114221048_create_topics.rb
class CreateTopics < ActiveRecord::Migra...
Sowing the Seeds
LEVEL 3 Model mayhem
/db/seeds.rb
$ rake db:seed
Run	
  from	
  command	
  line
mentions Won’t be set!
cl...
Sowing the Seeds
LEVEL 3 Model mayhem
/db/seeds.rb
:name => "Ruby5", :mentions => 2312
:name => "Top Ruby Jobs", :mentions...
Sowing the Seeds
LEVEL 3 Model mayhem
/db/seeds.rb
:name => "Ruby5", :mentions => 2312
:name => "Top Ruby Jobs", :mentions...
Sowing the Seeds
LEVEL 3 Model mayhem
/db/seeds.rb
:name => "Ruby5", :mentions => 2312
:name => "Top Ruby Jobs", :mentions...
Level 4Model Bert
82
N+1 is not for fun
/app/models/user.rb
class User
def recent_followers
self.followers.recent.collect{ |f| f.user.name }.to...
N+1 is not for fun
/app/models/user.rb
class User
def recent_followers
self.followers.recent .collect{ |f| f.user.name }.t...
counter_cache Money
/app/views/tweets/index.html.erb
<% @tweets.each do |tweet| %>
<div class="tweet">
<%= tweet.status %>...
counter_cache Money
/app/views/tweets/index.html.erb
<% @tweets.each do |tweet| %>
<div class="tweet">
<%= tweet.status %>...
counter_cache Money
/app/views/tweets/index.html.erb
<% @tweets.each do |tweet| %>
<div class="tweet">
<%= tweet.status %>...
counter_cache Money
/app/views/tweets/index.html.erb
<% @tweets.each do |tweet| %>
<div class="tweet">
<%= tweet.status %>...
counter_cache Money
/app/views/tweets/index.html.erb
<% @tweets.each do |tweet| %>
<div class="tweet">
<%= tweet.status %>...
counter_cache Money
Tweet Tweet
retweets
original_tweet
has_many
belongs_to
original retweet
/app/models/tweet.rb
has_many...
counter_cache Money
true:counter_cache =>
/app/models/tweet.rb
has_many :retweets,
:class_name => 'Tweet',
:foreign_key =>...
counter_cache Money
:counter_cache =>
/app/models/tweet.rb
has_many :retweets,
:class_name => 'Tweet',
:foreign_key => :tw...
counter_cache Money
t.retweets.length
pull	
  all	
  records
then	
  calls	
  .length
pull	
  all	
  records
then	
  calls...
Batches of find_each
/lib/tasks/long_running_task.rake
Not so good if you have millions of tweets
desc 'Task involving all...
Batches of find_each
/lib/tasks/long_running_task.rake
desc 'Task involving all tweets'
task :tweet_task => :environment d...
Batches of find_each
/lib/tasks/long_running_task.rake
desc 'Task involving all tweets'
task :tweet_task => :environment d...
Law of Demeter
Each unit should have limited knowledge about other units
“don’t talk to strangers”
tweet user account sett...
Law of Demeter
/app/models/tweet.rb
The tweet shouldn’t know about account_setting!
class Tweet < ActiveRecord::Base
def l...
Law of Demeter
/app/models/tweet.rb
class Tweet < ActiveRecord::Base
def location_data
if self.user.location_on_tweets
sel...
Law of Demeter
class User < ActiveRecord::Base
has_one :account_setting, :dependent => :destroy
end
delegate :location_on_...
Law of Demeter
class User < ActiveRecord::Base
has_one :account_setting, :dependent => :destroy
end
delegate :location_on_...
Head to to_s
/app/models/user.rb
<%= @user.display_name %>
class User < ActiveRecord::Base
def display_name
"#{first_name}...
Head to to_s
/app/models/user.rb
<%= @user %>
class User < ActiveRecord::Base
def to_s
"#{first_name} #{last_name}"
end
en...
to_param-alama ding dong
/app/models/topic.rb
/post/2133
class Topic < ActiveRecord::Base
def to_param
"#{id}-#{name.param...
to_param-alama ding dong
/app/models/topic.rb
class Topic < ActiveRecord::Base
def to_param
"#{id}-#{name.parameterize}"
e...
Level 5
Froggy Views
106
The example
LEVEL 5 Froggy Views
107
LEVEL 5 Froggy Views
No queries in your view!
/app/views/tweets/index.html.erb
current_user.who_to_follow.limit(5)<%
<li><...
LEVEL 5 Froggy Views
No queries in your view!
/app/views/tweets/index.html.erb
<%
<li><%= f.name %> - <%= link_to "Follow"...
LEVEL 5 Froggy Views
Helper Skelter
/app/views/tweets/index.html.erb
@followers_count %></span>
<% @recent_followers.each ...
LEVEL 5 Froggy Views
/app/views/tweets/index.html.erb
@followers_count @recent_followers
@following_count @recent_followin...
LEVEL 5 Froggy Views
/app/views/tweets/index.html.erb
@followers_count @recent_followers
@following_count @recent_followin...
LEVEL 5 Froggy Views
/app/views/tweets/index.html.erb
@followers_count @recent_followers
@following_count @recent_followin...
LEVEL 5 Froggy Views
/app/views/tweets/index.html.erb
@followers_count @recent_followers
@following_count @recent_followin...
The Example
LEVEL 5 Froggy Views
115
LEVEL 5 Froggy Views
Partial sanity
/app/views/tweets/index.html.erb
/app/views/tweets/_trending.html.erb
<h3><%= @user.tr...
LEVEL 5 Froggy Views
Partial sanity
/app/views/tweets/index.html.erb
/app/views/tweets/_trending.html.erb
<h3><%= @user.tr...
LEVEL 5 Froggy Views
Partial sanity
/app/views/tweets/index.html.erb
/app/views/tweets/_trending.html.erb
<h3><%=
@user.tr...
LEVEL 5 Froggy Views
Partial sanity
/app/views/tweets/_trending.html.erb
<h3><%= %></h3>
<ul>
<% .each do |topic| %>
<li>
...
LEVEL 5 Froggy Views
Partial sanity
/app/views/tweets/_trending.html.erb
<h3><%= %></h3>
<ul>
<% .each do |topic| %>
<li>
...
LEVEL 5 Froggy Views
Partial sanity
/app/views/tweets/_trending.html.erb
<h3><%= %></h3>
<li>
<%= link_to topic.name, topi...
LEVEL 5 Froggy Views
Partial sanity
/app/views/tweets/_trending.html.erb
<h3><%= %></h3>
<li>
<%= link_to topic.name, topi...
LEVEL 5 Froggy Views
empty string things
<% if @user.email.blank? %>
<% unless @user.email? %>
<% if @user.email.present? ...
LEVEL 5 Froggy Views
empty string things
<%= @user.city || @user.state || "Unknown" %>
If city is empty “” it will print “...
LEVEL 5 Froggy Views
empty string things
<%= @user.city || @user.state || "Unknown" %>
<%= @user.city.presence || @user.st...
LEVEL 5 Froggy Views
empty string things
city is nil
undefined method `titleize' for nil:NilClass
<%= @user.city.titleize ...
LEVEL 5 Froggy Views
empty string things
<%= @user.city.titleize %>
<% if @user.city %>
<% else %>
Unknown
<% end %>
<%= @...
The example
LEVEL 5 Froggy Views
128
LEVEL 5 Froggy Views
rock your block helpers
<% @presenter.tweets.each do |tweet| %>
<% end %>
<div id="tweet_<%= tweet.id...
LEVEL 5 Froggy Views
rock your block helpers
<% @presenter.tweets.each do |tweet| %>
<% end %>
<%= tweet.status %>
/app/vi...
LEVEL 5 Froggy Views
Yield to the content_for
/app/views/layouts/applica5on.html.erb
<!DOCTYPE html>
<html>
<body>
<h1>Twi...
LEVEL 5 Froggy Views
Yield to the content_for
/app/views/layouts/applica5on.html.erb
<!DOCTYPE html>
<html>
<body>
<h1>Twi...
LEVEL 5 Froggy Views
/app/controllers/tweets_controller.rb
what if all actions in the
tweet controller need the sidebar?
c...
LEVEL 5 Froggy Views
The example
134
LEVEL 5 Froggy Views
meta Yield
/app/views/layouts/applica5on.html.erb
cluttering your controller
& polluting with view co...
LEVEL 5 Froggy Views
meta Yield
/app/views/layouts/applica5on.html.erb
<%
content_for(:title, @tweet.user.name)
content_fo...
LEVEL 5 Froggy Views
meta Yield
<%
title @tweet.user.name
description @tweet.status
keywords @tweet.hash_tags.join(",")
%>...
Upcoming SlideShare
Loading in...5
×

Rails best practices_slides

2,755

Published on

Published in: Education, Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,755
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
39
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Transcript of "Rails best practices_slides"

  1. 1. 1
  2. 2. controllers In space 2
  3. 3. level 1 sad Code Happy Code controllers In space 3
  4. 4. LEVEL 1 controllers In Space Example 4
  5. 5. elsif .retweets.where(:user_id =>tweet tweet.user == current_userif current_user.id).present? LEVEL 1 controllers In Space FAT MODEL, SKINNY CONTROLLER /app/controllers/tweets_controller.rb class TweetsController < ApplicationController def retweet tweet = Tweet.find(params[:id]) flash[:notice] = flash[:notice] = "Sorry, you can't retweet your own tweets" "You already retweeted!" t = Tweet.new t.status = "RT #{tweet.user.name}: #{tweet.status}" t.original_tweet = tweet t.user = current_user t.save flash[:notice] = "Succesfully retweeted" redirect_to tweet end end end else 5
  6. 6. LEVEL 1 controllers In Space elsif .retweets.where(:user_id =>self self.user == retweeter FAT MODEL, SKINNY CONTROLLER /app/controllers/tweets_controller.rb class TweetsController < ApplicationController def retweet tweet = Tweet.find(params[:id]) flash[:notice] = "Sorry, you can't retweet your own tweets" "You already retweeted!" "Succesfully retweeted" redirect_to tweet end end end else ... tweet.retweet_by(current_user) /app/models/tweet.rb class Tweet < ActiveRecord::Base def retweet_by(retweeter) end end retweeter.id).present? if 6
  7. 7. @trending = Topic.find( :all, :conditions => ["started_trending > ?", 1.day.ago], :order => 'mentions desc', :limit => 5 ) LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb def index @tweets = Tweet.find( :all, :conditions => {:user_id => current_user.id}, : : 'created_at desc',order => => 10limit ... end ) 7
  8. 8. LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb def index @tweets = Tweet. ... end where( ). ( ). ( ) :user_id => current_user.id 'created_at desc'order limit 10 @trending = Topic. 'started_trending > ?', 1.day.ago order 'mentions desc' limit 5 where( ). ( ). ( ) order limit current_user def index @tweets = 8
  9. 9. LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb ... end ( ). ( ) 'created_at desc'order limit 10 current_user def index @tweets = .tweets. Scope to the user 9
  10. 10. LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb ... end ( ( ) 'created_at desc'order limit 10current_user def index @tweets = .tweets. /app/models/tweet.rb ... end ) recent. scope class Tweet < ActiveRecord::Base :recent, 10
  11. 11. default_ LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb ... end ( ( ) 'created_at desc' limit 10current_user def index @tweets = .tweets. /app/models/tweet.rb ... end )scope class Tweet < ActiveRecord::Base order 11
  12. 12. limit(5) LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb ... end /app/models/topic.rb ... end scope class Topic < ActiveRecord::Base :trending, def index @trending = Topic. 'started_trending > ?', 1.day.ago order 'mentions desc' where( ( trending ) ). where('started_trending > ?', '12-01-2010 14:02') Will only work once where('started_trending > ?', '12-01-2010 14:02') 1 2 same time! . 12
  13. 13. limit(5) LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb ... end /app/models/topic.rb ... end scope class Topic < ActiveRecord::Base :trending, def index @trending = Topic. 'started_trending > ?', 1.day.ago order 'mentions desc' where( ( trending ) ).lambda { } . 13
  14. 14. ||num LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb ... end /app/models/topic.rb ... end scope class Topic < ActiveRecord::Base :trending, def index @trending = Topic. 'started_trending > ?', 1.day.ago order 'mentions desc' where( ( ) ).lambda { } . limit(num) (5)trending @trending = Topic.trending(5) @trending = Topic.trending wrong number of args, 0 for 1 14
  15. 15. |num LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb ... end /app/models/topic.rb ... end scope class Topic < ActiveRecord::Base :trending, def index @trending = Topic. 'started_trending > ?', 1.day.ago order 'mentions desc' where( ( ) ).lambda { } . limit(num) (5)trending @trending = Topic.trending(5) @trending = Topic.trending |= nil Ruby 1.9 FTW! 15
  16. 16. default_ LEVEL 1 controllers In Space Scope it out ('created_at desc' /app/models/tweet.rb ... end )scope class Tweet < ActiveRecord::Base order How do we override default scope? order(:status).limit(10)@tweets = current_user.tweets.unscoped. @tweets = current_user.tweets.order(:status).limit(10) 16
  17. 17. LEVEL 1 controllers In Space Scope it out /app/controllers/tweets_controller.rb t = Tweet.new t.status = "RT #{@tweet.user.name}: #{@tweet.status}" t.original_tweet = @tweet current_user has many tweets.... t.user = current_user t.save current_user "RT #{@tweet.user.name}: #{@tweet.status}"status original_tweet @tweet : : => ) ,=> .tweets.create( 17
  18. 18. LEVEL 1 controllers In Space fantastic filters /app/controllers/tweets_controller.rb class TweetsController < ApplicationController end def edit def update def destroy @tweet = Tweet.find(params[:id]) end @tweet = Tweet.find(params[:id]) @tweet = Tweet.find(params[:id]) end end ... ... ... 18
  19. 19. LEVEL 1 controllers In Space /app/controllers/tweets_controller.rb class TweetsController < ApplicationController end def edit def update def destroy @tweet = Tweet.find(params[:id]) end @tweet = Tweet.find(params[:id])@tweet = Tweet.find(params[:id]) end end ... ... ... def get_tweet end before_filter :get_tweet, :only => [:edit, :update, :destroy] fantastic filters 19
  20. 20. LEVEL 1 controllers In Space @tweet = Tweet.find(params[:id]) /app/controllers/tweets_controller.rb class TweetsController < ApplicationController end def edit def update def destroy end end end ... ... ... def get_tweet end before_filter :get_tweet, :only => [:edit, :update, :destroy] private Why are you hiding instance variables? Tweet.find(params[:id])@tweet =@tweet =@tweet = get_tweetget_tweetget_tweet fantastic filters 20
  21. 21. @tweet = LEVEL 1 controllers In Space class TweetsController < ApplicationController end def edit def update def destroy end end end def get_tweet end private get_tweet @tweet = get_tweet @tweet = get_tweet Keeping  parameters  in  ac.ons params[:id])( params[:id])( params[:id])( (tweet_id) fantastic filters tweet_id)Tweet.find( 21
  22. 22. LEVEL 1 controllers In Space What should they be used for? authorization fantastic filters Logging wizards 22
  23. 23. LEVEL 1 controllers In Space fantastic filters /app/controllers/tweets_controller.rb class TweetsController < ApplicationController before_filter :auth, :only => [:edit, :update, :destroy] :except => [:index, :create] class ApplicationController < ActionController::Base before_filter :require_login class SessionsController < ApplicationController skip_before_filter :require_login, :only => [:new, :create] But what about the login page itself? Global  Filters 23
  24. 24. level 2 controller command 24
  25. 25. LEVEL 1 CONTROLLING YOUR CONTROLLERS Example 25
  26. 26. /app/models/user.rb class User < ActiveRecord::Base has_one :account_setting, :dependent => :destroy end <div class="field"> <%= a.label :public_email %><br /> <%= a.check_box :public_email %> </div> <div class="field"> <%= a.label :show_media %><br /> <%= a.check_box :show_media %> </div> <div class="field"> <%= a.label :protect_tweets %><br /> <%= a.check_box :protect_tweets %> </div> <% end %> <%= fields_for :account_setting do |a| %> Nested attributes /app/views/users/edit.html.erb LEVEL 2 controller command 26
  27. 27. Nested attributes /app/controllers/users_controller.rb @account_setting.save @user = User.new(params[:user]) @account_setting = AccountSetting.new(params[:account_setting]) class UsersController < ApplicationController def create if @user.save @account_setting.user = @user redirect_to(@user, :notice => 'User was successfully created.') else render :action => "new" end end end LEVEL 2 controller command 27
  28. 28. Nested attributes /app/controllers/users_controller.rb @user = User.new(params[:user]) class UsersController < ApplicationController def create if @user.save redirect_to(@user, :notice => 'User was successfully created.') else render :action => "new" end end end using  Nested  A8ributes LEVEL 2 controller command 28
  29. 29. /app/models/user.rb Nested attributes /app/views/users/edit.html.erb end class UsersController < ApplicationController def new @user = User.new end end /app/controllers/users_controller.rb (:account_setting => AccountSetting.new) accepts_nested_attributes_for :account_setting <%= ... f. class User < ActiveRecord::Base has_one :account_setting, :dependent => :destroy fields_for :account_setting do |a| %> <%= form_for(@user) do |f| %> LEVEL 2 controller command 29
  30. 30. Models without the database LEVEL 2 controller command 30
  31. 31. /app/views/contact_us/new.html.erb Models without the database <h1>Contact Us</h1> <%= form_for :contact, :url => send_email_path do |f| %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> </div> <div class="actions"> <%= f.submit %> </div> <% end %> LEVEL 2 controller command 31
  32. 32. /app/controllers/contact_us_controller.rb Models without the database class ContactUsController < ApplicationController def new end def send_email ).deliver flash[:notice] = "Email sent, we'll get back to you" redirect_to root_path end end end name, email, body name.blank? || email.blank? || body.blank? flash.now[:notice] = "Please fill out all fields" if name = params[:contact][:name] email = params[:contact][:email] body = params[:contact][:body] render :action => 'new' else Notifications.contact_us( LEVEL 2 controller command 32
  33. 33. @contact_form.valid?! Models without the database class ContactUsController < ApplicationController def new end def send_email ).deliver end end end if @contact_form = ContactForm.new(params[:contact_form]) @contact_form @contact_form = ContactForm.new /app/controllers/contact_us_controller.rb render :action => 'new' else Notifications.contact_us( flash[:notice] = "Email sent, we'll get back to you" redirect_to root_path LEVEL 2 controller command 33
  34. 34. @contact_form.valid? Models without the database class ContactUsController < ApplicationController def new end def send_email ).deliver end end end if @contact_form = ContactForm.new(params[:contact_form]) @contact_form @contact_form = ContactForm.new /app/controllers/contact_us_controller.rb render :action => 'new' else Notifications.contact_us( flash[:notice] = "Email sent, we'll get back to you" redirect_to root_path Use the positive inflection LEVEL 2 controller command 34
  35. 35. @contact_form.valid? Models without the database class ContactUsController < ApplicationController def new end def send_email ).deliver end end end if @contact_form = ContactForm.new(params[:contact_form]) @contact_form @contact_form = ContactForm.new /app/controllers/contact_us_controller.rb render :action => 'new' else Notifications.contact_us( use the redirect notice syntax :notice "Email sent, we'll get back to you"redirect_to root_path, => LEVEL 2 controller command 35
  36. 36. render @contact_form.valid? Models without the database class ContactUsController < ApplicationController def new end def send_email ).deliver end end end if @contact_form = ContactForm.new(params[:contact_form]) @contact_form @contact_form = ContactForm.new /app/controllers/contact_us_controller.rb else Notifications.contact_us( shorten the render :notice "Email sent, we'll get back to you"redirect_to root_path, => new: LEVEL 2 controller command 36
  37. 37. /app/views/contact_us/new.html.erb Models without the database <%= form_for , :url => send_email_path do |f| %> <h1>Contact Us</h1> @contact_form LEVEL 2 controller command 37
  38. 38. /app/models/contact_form.rb Models without the database class ContactForm attr_accessor :name, :email, :body end validates_presence_of :name, :email, :body include ActiveModel::Validations include ActiveModel::Conversion def initialize(attributes = {}) attributes.each do |name, value| send("#{name}=", value) end end def persisted? false end <%= form_for @contact_form ContactForm.new(params[:contact_form]) LEVEL 2 controller command 38
  39. 39. Models without the database LEVEL 2 controller command 39
  40. 40. really Rest class UsersController def < ApplicationController subscribe_mailing_list current_user.subscribe(params[:id]) redirect_to current_user, :notice => "You've been subscribed" end def unsubscribe_mailing_list current_user.unsubscribe(params[:id]) redirect_to current_user, :notice => "You have been unsubscribed" end end /app/controllers/users_controller.rb LEVEL 2 controller command 40
  41. 41. /app/controllers/subscrip5ons_controller.rb really Rest class SubscriptionsController def < ApplicationController create current_user.subscribe(params[:id]) redirect_to current_user, :notice => "You've been subscribed" end def destroy current_user.unsubscribe(params[:id]) redirect_to current_user, :notice => "You have been unsubscribed" end end LEVEL 2 controller command 41
  42. 42. really Rest Use your best judgement More than 2 levels is bad /users/1/posts/2/comments/3 Not using REST is okay /config/routes.rb get "contact_us/new" post "contact_us/send_email", :as => "send_email" LEVEL 2 controller command 42
  43. 43. Enter the Presenters LEVEL 2 controller command 43
  44. 44. Enter the Presenters /app/controllers/tweets_controller.rb @followers_tweets = current_user.followers_tweets.limit(20) @recent_tweet = current_user.tweets.first @following = current_user.following.limit(5) @followers = current_user.followers.limit(5) @recent_favorite = current_user.favorite_tweets.first @recent_listed = current_user.recently_listed.limit(5) if current_user.trend_option == "worldwide" @trends = Trend.worldwide.by_promoted.limit(10) else @trends = Trend.filter_by(current_user.trend_option).limit(10) end .... def index end LEVEL 2 controller command 44
  45. 45. Enter the Presenters /app/controllers/tweets_controller.rb def index end @presenter = Tweets::IndexPresenter.new(current_user) /config/applica5on.rb config.autoload_paths += [config.root.join("app/presenters")] /app/presenters/tweets/index_presenter.rb def initialize(user) @user = user end class Tweets::IndexPresenter LEVEL 2 controller command 45
  46. 46. Enter the Presenters /app/presenters/tweets/index_presenter.rb def initialize(user) @user = user end class Tweets::IndexPresenter def index end ... Old  Controller @followers_tweets = current_user.followers_tweets.limit(20) @recent_tweet = current_user.tweets.first if .trend_option == "worldwide"current_user .trend_option).limit(10) end current_user @trends = Trend.worldwide.by_promoted.limit(10) else @trends = Trend.filter_by( LEVEL 2 controller command 46
  47. 47. Enter the Presenters /app/presenters/tweets/index_presenter.rb def initialize(user) @user = user end class Tweets::IndexPresenter end .followers_tweets.limit(20) .tweets.first if .trend_option == "worldwide" .trend_option).limit(10) end def followers_tweets @user. end Trend.worldwide.by_promoted.limit(10) else Trend.filter_by(@user. @user. def recent_tweet @user end def trends LEVEL 2 controller command 47
  48. 48. Enter the Presenters /app/presenters/tweets/index_presenter.rb def initialize(user) @user = user end class Tweets::IndexPresenter /app/views/tweets/index.html.erb <%= @presenter.recent_tweet.created_at %> <%= @presenter.recent_tweet.body %> /app/controllers/tweets_controller.rb def index end @presenter = Tweets::IndexPresenter.new(current_user) .tweets.first def recent_tweet end Two objects! @user LEVEL 2 controller command 48
  49. 49. Enter the Presenters /app/presenters/tweets/index_presenter.rb def initialize(user) @user = user end class Tweets::IndexPresenter @recent_tweet ||= .tweets.first def recent_tweet @user /app/views/tweets/index.html.erb <%= @presenter.recent_tweet.created_at %> <%= @presenter.recent_tweet.body %> /app/controllers/tweets_controller.rb def index @presenter = Tweets::IndexPresenter.new(current_user) end end Memoized One object! LEVEL 2 controller command 49
  50. 50. Enter the Presenters /app/presenters/tweets/index_presenter.rb def initialize(user) @user = user end class Tweets::IndexPresenter .tweets.first def recent_tweet @user /app/views/tweets/index.html.erb <%= @presenter.recent_tweet.created_at %> <%= @presenter.recent_tweet.body %> /app/controllers/tweets_controller.rb def index @presenter = Tweets::IndexPresenter.new(current_user) end end extend ActiveSupport::Memoizable memoize :recent_tweet, :followers_tweet, ... LEVEL 2 controller command 50
  51. 51. Memoization one is better than the other value is not stored if false or nil is returned ||= extend ActiveSupport::Memoizable memoize :recent_tweet, :followers_tweet, ... def expensive(num) # lots of processing end memoize :expensive expensive(2) expensive(4) expensive(2) expensive(4) loaded from cache LEVEL 2 controller command 51
  52. 52. reject sql injection User.where("name = #{params[:name]}") User.where("name = ?", params[:name]) Tweet.where("created_at >= :start_date AND created_at <= :end_date", {:start_date => params[:start_date], :end_date => params[:end_date]}) Tweet.where(:created_at => (params[:start_date].to_date)..(params[:end_date].to_date)) User.where(:name => params[:name]) LEVEL 2 controller command 52
  53. 53. /app/controllers/users_controller.rb Rails 3 responder syntax do |format| format.html # show.html.erb format.xml { render :xml => @user } end respond_to do |format| format.html format.xml { render :xml => @users.to_xml } end respond_to def index @users = User.all end ... end def show @user = User.find(params[:id]) class UsersController < ApplicationController LEVEL 2 controller command 53
  54. 54. /app/controllers/users_controller.rb Rails 3 responder syntax respond_to def index @users = User.all end ... end def show @user = User.find(params[:id]) class UsersController < ApplicationController :html, :xml, :json respond_with(@users) respond_to respond_with(@user) LEVEL 2 controller command 54
  55. 55. level 3 Model Mayhem 55
  56. 56. LEVEL 3 Model mayhem Loving your indices current_user.tweets class AddIndexesToTables < ActiveRecord::Migration def self.up add_index :tweets, :user_id end def self.down remove_index :tweets, :user_id end end 56
  57. 57. LEVEL 3 Model mayhem current_user.tweets.order('created_at desc').limit(10) Topic.where("started_trending > ?", 1.day.ago).order('mentions desc').limit(5) class AddIndexesToTables < ActiveRecord::Migration def self.up add_index :tweets, [:user_id, :created_at] end def self.down end end remove_index :tweets, [:user_id, :created_at] remove_index :topics, [:started_trending, :mentions] add_index :topics, [:started_trending, :mentions] If these queries are run a great deal Loving your indices 57
  58. 58. Use your best judgement More indices, more time it takes to reindex If a 2 second query runs 5 times a week, LEVEL 3 Model mayhem who cares? Loving your indices 58
  59. 59. LEVEL 3 Model mayhem 59
  60. 60. LEVEL 3 Model mayhem 60
  61. 61. LEVEL 3 Model mayhem $ curl -d "user[login]=hacked&user[is_admin]=true&user[password] =password&user[password_confirmation]=password&user[email]=hacked@by.me" http://url_not_shown/users user[is_admin]=true 61
  62. 62. protecting your attributes /app/models/user.rb class User < ActiveRecord::Base attr_protected :is_admin end /app/models/user.rb class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation end whitelists are better for security LEVEL 3 Model mayhem 62
  63. 63. default values /app/models/account_se7ng.rb before_create :set_default_timezone def set_default_timezone self.time_zone = "EST" end end class AccountSetting < ActiveRecord::Base belongs_to :user LEVEL 3 Model mayhem 63
  64. 64. default values /app/models/account_se7ng.rb end class AccountSetting < ActiveRecord::Base belongs_to :user class AddDefaultTimeZoneToAccountSettings < ActiveRecord::Migration def self.up change_column_default :account_settings, :time_zone, 'EST' end def self.down change_column :account_settings, :time_zone, :string, nil end end /db/migrate/20110119150620_add_default_5me_zone_account_se7ngs.rb LEVEL 3 Model mayhem 64
  65. 65. Proper use of callbacks /app/models/topic.rb LEVEL 3 Model mayhem Time.now + (60 * 60 * 24 * 7)self.finish_trending = end end class Topic < ActiveRecord::Base before_create :set_trend_ending private def set_trend_ending 65
  66. 66. Proper use of callbacks /app/models/topic.rb LEVEL 3 Model mayhem 1.weekself.finish_trending = end end .from_now class Topic < ActiveRecord::Base before_create :set_trend_ending private def set_trend_ending 66
  67. 67. Proper use of callbacks /app/models/topic.rb before_create :set_trend_ending private def set_trend_ending LEVEL 3 Model mayhem 1.week self.finish_trending = end end .from_now class Topic < ActiveRecord::Base TRENDING_PERIOD = TRENDING_PERIOD 67
  68. 68. Proper use of callbacks LEVEL 3 Model mayhem Crea;ng  an  object before_validation after_validation before_save after_save before_create around_create after_create Upda;ng  an  object Dele;ng  an  object before_validation after_validation before_save after_save before_update around_update after_update before_destroy after_destroy around_destroy 68
  69. 69. Rails date helpers LEVEL 3 Model mayhem Date  Helpers 1.minute 2.hour 3.days 4.week 5.months 6.year Modifiers More  Modifiers beginning_of_day beginning_of_week beginning_of_month beginning_of_quarter beginning_of_year 2.weeks.ago 3.weeks.from_now next_week next_month next_year * singular or plural * end can be used * prev can be used 69
  70. 70. Proper use of callbacks LEVEL 3 Model mayhem 70
  71. 71. Proper use of callbacks LEVEL 3 Model mayhem /app/models/following.rb class Following < ActiveRecord::Base after_create :send_follower_notification def send_follower_notification if queue_new_follower_email end end end self.followed_user.receive_emails? 71
  72. 72. Proper use of callbacks LEVEL 3 Model mayhem /app/models/following.rb class Following < ActiveRecord::Base after_create : self.followed_user.receive_emails? def followed_can_receive_emails? queue_new_follower_email end end :if => :followed_can_receive_emails? queue_new_follower_email, 72
  73. 73. Proper use of callbacks LEVEL 3 Model mayhem /app/models/following.rb class Following < ActiveRecord::Base after_create :queue_new_follower_email end :if => , Proc.new {|f| .followed_user.receive_emails?f } 73
  74. 74. improved validation LEVEL 3 Model mayhem .errors.add(:name, 'is inappropriate') selfunless ContentModerator.is_suitable?( .name) self end /app/models/topic.rb end end def validate class Topic < ActiveRecord::Base 74
  75. 75. improved validation LEVEL 3 Model mayhem .errors.add(:name, 'is inappropriate') /app/models/topic.rb end end class Topic < ActiveRecord::Base validate :appropriate_content private def appropriate_content selfunless ContentModerator.is_suitable?( .name) self end 75
  76. 76. improved validation LEVEL 3 Model mayhem /app/models/topic.rb end end class Topic < ActiveRecord::Base validates :name, :appropriate => true end /lib/appropriate_validator.rb class AppropriateValidator < ActiveRecord::EachValidator def validate_each(record, attribute, value) .errors.add( , 'is inappropriate') unless ContentModerator.is_suitable?(value) end record attribute Don’t forget to require this /lib  isn’t  auto-­‐loaded  by  default 76
  77. 77. Sowing the Seeds LEVEL 3 Model mayhem /db/migrate/20110114221048_create_topics.rb class CreateTopics < ActiveRecord::Migration def self.up create_table :topics do |t| t.string :name t.datetime :started_trending t.integer :mentions t.timestamps end end def self.down drop_table :topics end end Topic.create( Topic.create( Topic.create( ) ):name => "Ruby5", :mentions => 2312 :name => "Top Ruby Jobs", :mentions => 231 :name => "Rails for Zombies", :mentions => 1023) 77
  78. 78. Sowing the Seeds LEVEL 3 Model mayhem /db/seeds.rb $ rake db:seed Run  from  command  line mentions Won’t be set! class Topic < ActiveRecord::Base attr_protected :mentions end /app/models/topic.rb Topic.create( Topic.create( Topic.create( ) ) ):name => "Ruby5", :mentions => 2312 :name => "Top Ruby Jobs", :mentions => 231 :name => "Rails for Zombies", :mentions => 1023 78
  79. 79. Sowing the Seeds LEVEL 3 Model mayhem /db/seeds.rb :name => "Ruby5", :mentions => 2312 :name => "Top Ruby Jobs", :mentions => 231 :name => "Rails for Zombies", :mentions => 1023 topics.each do |attributes| Topic.create do |t| t.name = attributes[:name] topics = [ { }, { }, { } ] What if we want to be able to update the seed? t.mentions = attributes[:mentions] end end 79
  80. 80. Sowing the Seeds LEVEL 3 Model mayhem /db/seeds.rb :name => "Ruby5", :mentions => 2312 :name => "Top Ruby Jobs", :mentions => 231 :name => "Rails for Zombies", :mentions => 1023 topics.each do |attributes| Topic.create do |t| t.name = attributes[:name] topics = [ { }, { }, { } ] Topic.destroy_all Dangerous if there are lots of relationships t.mentions = attributes[:mentions] end end 80
  81. 81. Sowing the Seeds LEVEL 3 Model mayhem /db/seeds.rb :name => "Ruby5", :mentions => 2312 :name => "Top Ruby Jobs", :mentions => 231 :name => "Rails for Zombies", :mentions => 1023 topics.each do |attributes| Topic. attributes[:name] topics = [ { }, { }, { } ] find_or_initialize_by_name( ).tap do |t| t.mentions = attributes[:mentions] end end t.save! 81
  82. 82. Level 4Model Bert 82
  83. 83. N+1 is not for fun /app/models/user.rb class User def recent_followers self.followers.recent.collect{ |f| f.user.name }.to_sentence end end => "Gregg, Eric, Dray, and Nate" Select followers where user_id=1 Select user where id=2 Select user where id=3 Select user where id=4 Select user where id=5 LEVEL 4 Model Bert 83
  84. 84. N+1 is not for fun /app/models/user.rb class User def recent_followers self.followers.recent .collect{ |f| f.user.name }.to_sentence end end .includes(:user) Select followers where user_id=1 Select users where user_id in (2,3,4,5) 2 queries instead of 5! h"ps://github.com/flyerhzm/bullet Bullet gem To find all your n+1 queries LEVEL 4 Model Bert 84
  85. 85. counter_cache Money /app/views/tweets/index.html.erb <% @tweets.each do |tweet| %> <div class="tweet"> <%= tweet.status %> <span class="retweets"> <%= tweet.retweets.length ReTweets%> </span> </div> <% end %> 2 ReTweets 1 ReTweets 0 ReTweets Bad English LEVEL 4 Model Bert 85
  86. 86. counter_cache Money /app/views/tweets/index.html.erb <% @tweets.each do |tweet| %> <div class="tweet"> <%= tweet.status %> <span class="retweets"> <%= tweet.retweets.length %> </span> </div> <% end %> pluralize( , "ReTweet") 2 ReTweets 1 ReTweet 0 ReTweets LEVEL 4 Model Bert 86
  87. 87. counter_cache Money /app/views/tweets/index.html.erb <% @tweets.each do |tweet| %> <div class="tweet"> <%= tweet.status %> <span class="retweets"> <%= tweet.retweets.length %> </span> </div> <% end %> , "ReTweet") 1. Select all retweets where user_id=X 2. Populate an array of tweet objects 3. Call length on that array For Each tweet Lots of unneeded objects pluralize( LEVEL 4 Model Bert 87
  88. 88. counter_cache Money /app/views/tweets/index.html.erb <% @tweets.each do |tweet| %> <div class="tweet"> <%= tweet.status %> <span class="retweets"> <%= tweet.retweets.count %> </span> </div> <% end %> pluralize( , "ReTweet") 1. Select all retweets where user_id=X 2. do a count query for retweets For Each tweet possibly 10+ count queries LEVEL 4 Model Bert 88
  89. 89. counter_cache Money /app/views/tweets/index.html.erb <% @tweets.each do |tweet| %> <div class="tweet"> <%= tweet.status %> <span class="retweets"> <%= tweet.retweets.size %> </span> </div> <% end %> pluralize( , "ReTweet") 1. Select all retweets where user_id=X For Each tweet There is no step 2 with  counter_cache 89
  90. 90. counter_cache Money Tweet Tweet retweets original_tweet has_many belongs_to original retweet /app/models/tweet.rb has_many :retweets, :class_name => 'Tweet', :foreign_key => :tweet_id end class Tweet < ActiveRecord::Base belongs_to :original_tweet, :class_name => 'Tweet', :foreign_key => :tweet_id class AddCountRetweets def self.up add_column :tweets, :retweets_count, :integer, :default => 0 end ... Our  migra;on retweets_count LEVEL 4 Model Bert 90
  91. 91. counter_cache Money true:counter_cache => /app/models/tweet.rb has_many :retweets, :class_name => 'Tweet', :foreign_key => :tweet_id end class Tweet < ActiveRecord::Base belongs_to :original_tweet, :class_name => 'Tweet', :foreign_key => :tweet_id retweets_count , unable to find tweets_count LEVEL 4 Model Bert 91
  92. 92. counter_cache Money :counter_cache => /app/models/tweet.rb has_many :retweets, :class_name => 'Tweet', :foreign_key => :tweet_id end class Tweet < ActiveRecord::Base belongs_to :original_tweet, :class_name => 'Tweet', :foreign_key => :tweet_id, retweets_count: current_user.tweets.create( :status => "RT #{self.user.name}: #{self.status}", :original_tweet => self ) UPDATE "tweets" SET "retweets_count" = "retweets_count" + 1 WHERE ("tweets"."id" = 42) will cause an insert AND LEVEL 4 Model Bert 92
  93. 93. counter_cache Money t.retweets.length pull  all  records then  calls  .length pull  all  records then  calls  .length t.retweets.count count  query count  query t.retweets.size count  query no  query look  at  cache Without Cache Counter With Cache Counter LEVEL 4 Model Bert 93
  94. 94. Batches of find_each /lib/tasks/long_running_task.rake Not so good if you have millions of tweets desc 'Task involving all tweets' task :tweet_task => :environment do Tweet.all.each do |tweet| p "task for #{tweet}" end end LEVEL 4 Model Bert 94
  95. 95. Batches of find_each /lib/tasks/long_running_task.rake desc 'Task involving all tweets' task :tweet_task => :environment do Tweet each do |tweet| p "task for #{tweet}" end end .find_ pulls batches of 1,000 at a time LEVEL 4 Model Bert 95
  96. 96. Batches of find_each /lib/tasks/long_running_task.rake desc 'Task involving all tweets' task :tweet_task => :environment do Tweet each do |tweet| p "task for #{tweet}" end end .find_ (:batch_size => 200) pulls batches of 200 at a time LEVEL 4 Model Bert 96
  97. 97. Law of Demeter Each unit should have limited knowledge about other units “don’t talk to strangers” tweet user account settings X LEVEL 4 Model Bert 97
  98. 98. Law of Demeter /app/models/tweet.rb The tweet shouldn’t know about account_setting! class Tweet < ActiveRecord::Base def location_data if self.user.account_setting.location_on_tweets self.location else "unavailable" end end end LEVEL 4 Model Bert 98
  99. 99. Law of Demeter /app/models/tweet.rb class Tweet < ActiveRecord::Base def location_data if self.user.location_on_tweets self.location else "unavailable" end end end class User < ActiveRecord::Base has_one :account_setting, :dependent => :destroy end delegate :location_on_tweets, /app/models/user.rb :to => :account_setting :public_email, Additional Methods LEVEL 4 Model Bert 99
  100. 100. Law of Demeter class User < ActiveRecord::Base has_one :account_setting, :dependent => :destroy end delegate :location_on_tweets, /app/models/user.rb :to => :account_setting :public_email, tweet user account settings X self.user.location_on_tweets ERROR!! account_setting is nil! LEVEL 4 Model Bert 100
  101. 101. Law of Demeter class User < ActiveRecord::Base has_one :account_setting, :dependent => :destroy end delegate :location_on_tweets, /app/models/user.rb :to => :account_setting :public_email tweet user account settings X self.user.location_on_tweets :allow_nil => true , , Returns nil when Account_Settings is missing LEVEL 4 Model Bert 101
  102. 102. Head to to_s /app/models/user.rb <%= @user.display_name %> class User < ActiveRecord::Base def display_name "#{first_name} #{last_name}" end end LEVEL 4 Model Bert 102
  103. 103. Head to to_s /app/models/user.rb <%= @user %> class User < ActiveRecord::Base def to_s "#{first_name} #{last_name}" end end LEVEL 4 Model Bert 103
  104. 104. to_param-alama ding dong /app/models/topic.rb /post/2133 class Topic < ActiveRecord::Base def to_param "#{id}-#{name.parameterize}" end end /post/rails-best-practices SEO Friendly URLS /post/2133-rails-best-practices LEVEL 4 Model Bert 104
  105. 105. to_param-alama ding dong /app/models/topic.rb class Topic < ActiveRecord::Base def to_param "#{id}-#{name.parameterize}" end end /post/2133-rails-best-practices <%= link_to topic.name, topic %> Will  generate Topic.find(params[:id]) {:id => "2133-rails-best-practices"} Will  call  to_i Topic.find(2133) LEVEL 4 Model Bert 105
  106. 106. Level 5 Froggy Views 106
  107. 107. The example LEVEL 5 Froggy Views 107
  108. 108. LEVEL 5 Froggy Views No queries in your view! /app/views/tweets/index.html.erb current_user.who_to_follow.limit(5)<% <li><%= f.name %> - <%= link_to "Follow", follow_user_path(f) %></li> <% end %> .each do |f| %> Query shouldn’t be in our view! 108
  109. 109. LEVEL 5 Froggy Views No queries in your view! /app/views/tweets/index.html.erb <% <li><%= f.name %> - <%= link_to "Follow", follow_user_path(f) %></li> <% end %> .each do |f| %> /app/controllers/tweets_controller.rb class TweetsController < ApplicationController def index @who_to_follow = end end current_user.who_to_follow.limit(5) @who_to_follow 109
  110. 110. LEVEL 5 Froggy Views Helper Skelter /app/views/tweets/index.html.erb @followers_count %></span> <% @recent_followers.each do |f| %> <a href="<%= user_path(f) %>"> <img src="<%= f.avatar.url(:thumb) %>" /> </a> <% end %> </div> <div class="following"> <div class="followers"> Followers <span><%= Following <span><%= @following_count %></span> <% @recent_following.each do |f| %> <a href="<%= user_path(f) %>"> <img src="<%= f.avatar.url(:thumb) %>" /> </a> <% end %> </div> 110
  111. 111. LEVEL 5 Froggy Views /app/views/tweets/index.html.erb @followers_count @recent_followers @following_count @recent_following <%= follow_box( <%= follow_box( "Followers", , ) %> "Following", , ) %> Followers Following /app/helpers/tweets_helper.rb def follow_box(title, count, recent) end end str = "<div class="#{title.downcase ">" + "#{title}<span>#{count}</span>" recent.each do |user| str += "<a href="#{user_path(user)}">" str += "<img src="#{user.avatar.url(:thumb)}">" str += "</a>" Use proper link_to and image_tag += "</div>")raw(str } Helper Skelter 111
  112. 112. LEVEL 5 Froggy Views /app/views/tweets/index.html.erb @followers_count @recent_followers @following_count @recent_following <%= follow_box( <%= follow_box( "Followers", , ) %> "Following", , ) %> def follow_box(title, count, recent) end end str = "<div class="#{title.downcase ">" + "#{title}<span>#{count}</span>" recent.each do |user| str += user.avatar.url(:thumb) link_to user do image_tag( end ) Use html helpers? += "</div>")raw(str /app/helpers/tweets_helper.rb Helper Skelter 112
  113. 113. LEVEL 5 Froggy Views /app/views/tweets/index.html.erb @followers_count @recent_followers @following_count @recent_following <%= follow_box( <%= follow_box( "Followers", , ) %> "Following", , ) %> def follow_box(title, count, recent) end end title.downcase title count recent.each do |user| str += user.avatar.url(:thumb) link_to user do image_tag( end ) ) content_tag :div, :class => do str = ) end raw(str + content_tag(:span, Annoying str variable /app/helpers/tweets_helper.rb Helper Skelter 113
  114. 114. LEVEL 5 Froggy Views /app/views/tweets/index.html.erb @followers_count @recent_followers @following_count @recent_following <%= follow_box( <%= follow_box( "Followers", , ) %> "Following", , ) %> def follow_box(title, count, recent) end end title.downcase title count recent. user.avatar.url(:thumb) link_to user do image_tag( end ) ) content_tag :div, :class => do ) end raw( + content_tag(:span, + collect do |user| .join /app/helpers/tweets_helper.rb Helper Skelter 114
  115. 115. The Example LEVEL 5 Froggy Views 115
  116. 116. LEVEL 5 Froggy Views Partial sanity /app/views/tweets/index.html.erb /app/views/tweets/_trending.html.erb <h3><%= @user.trending_area %></h3> <ul> <% @trending.each do |topic| %> <li> <%= link_to topic.name, topic %> <% if topic.promoted? %> <%= link_to image_tag('promoted.jpg'), topic %> <% end %> </li> <% end %> </ul> <%= render :partial => 'trending' %> <h2>Trends</h2> 116
  117. 117. LEVEL 5 Froggy Views Partial sanity /app/views/tweets/index.html.erb /app/views/tweets/_trending.html.erb <h3><%= @user.trending_area %></h3> <ul> <% @trending.each do |topic| %> <li> <%= link_to topic.name, topic %> <% if topic.promoted? %> <%= link_to image_tag('promoted.jpg'), topic %> <% end %> </li> <% end %> </ul> <%= render 'trending' %> <h2>Trends</h2> There are instance variables in our partial! 117
  118. 118. LEVEL 5 Froggy Views Partial sanity /app/views/tweets/index.html.erb /app/views/tweets/_trending.html.erb <h3><%= @user.trending_area %></h3> <ul> <% @trending .each do |topic| %> <li> <%= link_to topic.name, topic %> <% if topic.promoted? %> <%= link_to image_tag('promoted.jpg'), topic %> <% end %> </li> <% end %> </ul> <%= render 'trending' %> <h2>Trends</h2> , :area => , :topics => area topics 118
  119. 119. LEVEL 5 Froggy Views Partial sanity /app/views/tweets/_trending.html.erb <h3><%= %></h3> <ul> <% .each do |topic| %> <li> <%= link_to topic.name, topic %> <% if topic.promoted? %> <%= link_to image_tag('promoted.jpg'), topic %> <% end %> </li> <% end %> </ul> area topics /app/views/topics/_topic.html.erb <%= render topic %>'topics/topic', :topic => 119
  120. 120. LEVEL 5 Froggy Views Partial sanity /app/views/tweets/_trending.html.erb <h3><%= %></h3> <ul> <% .each do |topic| %> <li> <%= link_to topic.name, topic %> <% if topic.promoted? %> <%= link_to image_tag('promoted.jpg'), topic %> <% end %> </li> <% end %> </ul> area topics /app/views/topics/_topic.html.erb <%= render topic %> <ul> Using Class name to find partial 120
  121. 121. LEVEL 5 Froggy Views Partial sanity /app/views/tweets/_trending.html.erb <h3><%= %></h3> <li> <%= link_to topic.name, topic %> <% if topic.promoted? %> <%= link_to image_tag('promoted.jpg'), topic %> <% end %> </li> </ul> area topics /app/views/topics/_topic.html.erb <%= render <ul> %>:partial => 'topics/topic', :collection => 121
  122. 122. LEVEL 5 Froggy Views Partial sanity /app/views/tweets/_trending.html.erb <h3><%= %></h3> <li> <%= link_to topic.name, topic %> <% if topic.promoted? %> <%= link_to image_tag('promoted.jpg'), topic %> <% end %> </li> </ul> area topics /app/views/topics/_topic.html.erb <%= render <ul> %> 122
  123. 123. LEVEL 5 Froggy Views empty string things <% if @user.email.blank? %> <% unless @user.email? %> <% if @user.email.present? %> <% if @user.email? %> 123
  124. 124. LEVEL 5 Froggy Views empty string things <%= @user.city || @user.state || "Unknown" %> If city is empty “” it will print “” city = @user.city if @user.city.present? state = @user.state if @user.state.present? => “” <%= city || state || "Unknown" %> 124
  125. 125. LEVEL 5 Froggy Views empty string things <%= @user.city || @user.state || "Unknown" %> <%= @user.city.presence || @user.state.presence || "Unknown" %> 125
  126. 126. LEVEL 5 Froggy Views empty string things city is nil undefined method `titleize' for nil:NilClass <%= @user.city.titleize %>|| "Unknown" 126
  127. 127. LEVEL 5 Froggy Views empty string things <%= @user.city.titleize %> <% if @user.city %> <% else %> Unknown <% end %> <%= @user.city ? @user.city.titleize : "Unknown" %> <%= @user.city.try(:titleize) || "Unknown" %> 127
  128. 128. The example LEVEL 5 Froggy Views 128
  129. 129. LEVEL 5 Froggy Views rock your block helpers <% @presenter.tweets.each do |tweet| %> <% end %> <div id="tweet_<%= tweet.id %>" class="<%= 'favorite' if tweet.is_a_favorite?(current_user) %>"> <%= tweet.status %> </div> /app/views/tweets/index.html.erb 129
  130. 130. LEVEL 5 Froggy Views rock your block helpers <% @presenter.tweets.each do |tweet| %> <% end %> <%= tweet.status %> /app/views/tweets/index.html.erb <%= tweet_div_for(tweet, current_user) do %> <% end %> /app/helpers/tweets_helper.rb def tweet_div_for(tweet, user, &block) klass = 'favorite' if tweet.is_a_favorite?(user) content_tag tweet klass do yield end end , :class => id="tweet_<%= tweet.id %>" 130
  131. 131. LEVEL 5 Froggy Views Yield to the content_for /app/views/layouts/applica5on.html.erb <!DOCTYPE html> <html> <body> <h1>Twitter</h1> Need to insert content here! <% if flash[:notice] %> <span style="color: green"><%= flash[:notice] %></span> <% end %> <%= yield %> </body> </html> 131
  132. 132. LEVEL 5 Froggy Views Yield to the content_for /app/views/layouts/applica5on.html.erb <!DOCTYPE html> <html> <body> <h1>Twitter</h1> <%= yield :sidebar %> /app/views/tweets/index.html.erb <% content_for(:sidebar) do %> ... html here ... <% end %> <% if flash[:notice] %> <span style="color: green"><%= flash[:notice] %></span> <% end %> <%= yield %> </body> </html> 132
  133. 133. LEVEL 5 Froggy Views /app/controllers/tweets_controller.rb what if all actions in the tweet controller need the sidebar? class TweetsController < ApplicationController layout 'with_sidebar' end /app/views/layouts/with_sidebar.html.erb <% content_for(:sidebar) do %> ... html here ... <% end %> <%= render :file => 'layouts/application' %> /app/views/layouts/applica5on.html.erb <%= yield :sidebar %> <% if flash[:notice] %> <span style="color: green"><%= flash[:notice] %></span> <% end %> <%= yield %> 1 2 3 133
  134. 134. LEVEL 5 Froggy Views The example 134
  135. 135. LEVEL 5 Froggy Views meta Yield /app/views/layouts/applica5on.html.erb cluttering your controller & polluting with view concerns class TweetsController < ApplicationController def show @tweet = Tweet.find(params[:id]) @title = @tweet.user.name @description = @tweet.status @keywords = @tweet.hash_tags.join(",") end end /app/controllers/tweets_controller.rb @description || "The best way ..." %>"> <meta name ="keywords" content="<%= @keywords || "social,tweets ..." %>"> ... <!DOCTYPE html> <html> <head> <title>Twitter <%= %></title> <meta name="description" content="<%= @title 135
  136. 136. LEVEL 5 Froggy Views meta Yield /app/views/layouts/applica5on.html.erb <% content_for(:title, @tweet.user.name) content_for(:description, @tweet.status) content_for(:keywords, @tweet.hash_tags.join(",")) %> /app/views/tweets/show.html.erb <meta name ="keywords" content="<%= ... <!DOCTYPE html> <html> <head> <title>Twitter <%= %></title> <meta name="description" content="<%= yield(:title) yield(:description) yield(:keywords) || "The best way ..." %>"> || "social,tweets ..." %>"> 136
  137. 137. LEVEL 5 Froggy Views meta Yield <% title @tweet.user.name description @tweet.status keywords @tweet.hash_tags.join(",") %> /app/views/tweets/show.html.erb /app/helpers/applica5on_helper.rb def title(title) content_for(:title, title) end def description(description) content_for(:description, description) end def keywords(keywords) content_for(:keywords, keywords) end 137
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×