rails best practices_slides

137
1

Post on 12-Sep-2014

986 views

Category:

Education


1 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Rails best practices_slides

1

Page 2: Rails best practices_slides

controllers In space

2

Page 3: Rails best practices_slides

level 1

sad Code

Happy Code

controllers In space

3

Page 4: Rails best practices_slides

LEVEL 1 controllers In Space

Example

4

Page 5: Rails best practices_slides

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 endend

end

else

5

Page 6: Rails best practices_slides

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 endend

end

else...

tweet.retweet_by(current_user)

/app/models/tweet.rb

class Tweet < ActiveRecord::Base def retweet_by(retweeter)

endend

retweeter.id).present?

if

6

Page 7: Rails best practices_slides

@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

Page 8: Rails best practices_slides

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.agoorder 'mentions desc'limit 5

where( ).( ).( )

orderlimit

current_userdef index @tweets =

8

Page 9: Rails best practices_slides

LEVEL 1 controllers In Space

Scope it out/app/controllers/tweets_controller.rb

...end

( ).( )'created_at desc'order

limit 10

current_userdef index @tweets = .tweets.

Scope to the user

9

Page 10: Rails best practices_slides

LEVEL 1 controllers In Space

Scope it out/app/controllers/tweets_controller.rb

...end

(

( )

'created_at desc'order

limit 10current_userdef index @tweets = .tweets.

/app/models/tweet.rb

...end

)

recent.

scopeclass Tweet < ActiveRecord::Base

:recent,

10

Page 11: Rails best practices_slides

default_

LEVEL 1 controllers In Space

Scope it out/app/controllers/tweets_controller.rb

...end

(

( )

'created_at desc'

limit 10current_userdef index @tweets = .tweets.

/app/models/tweet.rb

...end

)scopeclass Tweet < ActiveRecord::Base

order

11

Page 12: Rails best practices_slides

limit(5)

LEVEL 1 controllers In Space

Scope it out/app/controllers/tweets_controller.rb

...end

/app/models/topic.rb

...end

scopeclass Topic < ActiveRecord::Base

:trending,

def index@trending = Topic.

'started_trending > ?', 1.day.agoorder '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

Page 13: Rails best practices_slides

limit(5)

LEVEL 1 controllers In Space

Scope it out/app/controllers/tweets_controller.rb

...end

/app/models/topic.rb

...end

scopeclass Topic < ActiveRecord::Base

:trending,

def index@trending = Topic.

'started_trending > ?', 1.day.agoorder 'mentions desc'where(

(

trending

)).lambda {

}

.

13

Page 14: Rails best practices_slides

||num

LEVEL 1 controllers In Space

Scope it out/app/controllers/tweets_controller.rb

...end

/app/models/topic.rb

...end

scopeclass Topic < ActiveRecord::Base

:trending,

def index@trending = Topic.

'started_trending > ?', 1.day.agoorder 'mentions desc'where(

( )).lambda {

}.

limit(num)

(5)trending

@trending = Topic.trending(5)

@trending = Topic.trending

wrong number of args, 0 for 1

14

Page 15: Rails best practices_slides

|num

LEVEL 1 controllers In Space

Scope it out/app/controllers/tweets_controller.rb

...end

/app/models/topic.rb

...end

scopeclass Topic < ActiveRecord::Base

:trending,

def index@trending = Topic.

'started_trending > ?', 1.day.agoorder 'mentions desc'where(

( )).lambda {

}.

limit(num)

(5)trending

@trending = Topic.trending(5)

@trending = Topic.trending

| = nil

Ruby 1.9 FTW!

15

Page 16: Rails best practices_slides

default_

LEVEL 1 controllers In Space

Scope it out

('created_at desc'

/app/models/tweet.rb

...end

)scopeclass 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

Page 17: Rails best practices_slides

LEVEL 1 controllers In Space

Scope it out/app/controllers/tweets_controller.rb

t = Tweet.newt.status = "RT #{@tweet.user.name}: #{@tweet.status}"t.original_tweet = @tweet

current_user has many tweets....

t.user = current_usert.save

current_user"RT #{@tweet.user.name}: #{@tweet.status}"status

original_tweet @tweet:: =>)

,=>.tweets.create(

17

Page 18: Rails best practices_slides

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

Page 19: Rails best practices_slides

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

Page 20: Rails best practices_slides

LEVEL 1 controllers In Space

@tweet = Tweet.find(params[:id])

/app/controllers/tweets_controller.rb

class TweetsController < ApplicationController

end

def edit

def update

def destroyend

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

Page 21: Rails best practices_slides

@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

Page 22: Rails best practices_slides

LEVEL 1 controllers In Space

What should they be used for?

authorization

fantastic filters

Loggingwizards

22

Page 23: Rails best practices_slides

LEVEL 1 controllers In Space

fantastic filters/app/controllers/tweets_controller.rb

class TweetsController < ApplicationControllerbefore_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

Page 24: Rails best practices_slides

level 2controller command

24

Page 25: Rails best practices_slides

LEVEL 1 CONTROLLING YOUR CONTROLLERS

Example

25

Page 26: Rails best practices_slides

/app/models/user.rb

class User < ActiveRecord::Base has_one :account_setting, :dependent => :destroyend

<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 command26

Page 27: Rails best practices_slides

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 command27

Page 28: Rails best practices_slides

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 command28

Page 29: Rails best practices_slides

/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 command29

Page 30: Rails best practices_slides

Models without the database

LEVEL 2 controller command30

Page 31: Rails best practices_slides

/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 command31

Page 32: Rails best practices_slides

/app/controllers/contact_us_controller.rb

Models without the database

class ContactUsController < ApplicationController

def new end

def send_email

).deliverflash[:notice] = "Email sent, we'll get back to you"redirect_to root_path

end endend

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'elseNotifications.contact_us(

LEVEL 2 controller command32

Page 33: Rails best practices_slides

@contact_form.valid?!

Models without the database

class ContactUsController < ApplicationController

def new

end

def send_email

).deliver

end endend

if

@contact_form = ContactForm.new(params[:contact_form])

@contact_form

@contact_form = ContactForm.new

/app/controllers/contact_us_controller.rb

render :action => 'new'elseNotifications.contact_us(flash[:notice] = "Email sent, we'll get back to you"redirect_to root_path

LEVEL 2 controller command33

Page 34: Rails best practices_slides

@contact_form.valid?

Models without the database

class ContactUsController < ApplicationController

def new

end

def send_email

).deliver

end endend

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 command34

Page 35: Rails best practices_slides

@contact_form.valid?

Models without the database

class ContactUsController < ApplicationController

def new

end

def send_email

).deliver

end endend

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 command35

Page 36: Rails best practices_slides

render

@contact_form.valid?

Models without the database

class ContactUsController < ApplicationController

def new

end

def send_email

).deliver

end endend

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 command36

Page 37: Rails best practices_slides

/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 command37

Page 38: Rails best practices_slides

/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::Validationsinclude 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 command38

Page 39: Rails best practices_slides

Models without the database

LEVEL 2 controller command39

Page 40: Rails best practices_slides

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 command40

Page 41: Rails best practices_slides

/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 command41

Page 42: Rails best practices_slides

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 command42

Page 43: Rails best practices_slides

Enter the Presenters

LEVEL 2 controller command43

Page 44: Rails best practices_slides

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 command44

Page 45: Rails best practices_slides

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 command45

Page 46: Rails best practices_slides

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 command46

Page 47: Rails best practices_slides

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)elseTrend.filter_by(@user.

@user.

def recent_tweet@user

end

def trends

LEVEL 2 controller command47

Page 48: Rails best practices_slides

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.firstdef recent_tweet

end

Two objects!

@user

LEVEL 2 controller command48

Page 49: Rails best practices_slides

Enter the Presenters/app/presenters/tweets/index_presenter.rb

def initialize(user) @user = user end

class Tweets::IndexPresenter

@recent_tweet ||= .tweets.firstdef 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 command49

Page 50: Rails best practices_slides

Enter the Presenters/app/presenters/tweets/index_presenter.rb

def initialize(user) @user = user end

class Tweets::IndexPresenter

.tweets.firstdef 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 command50

Page 51: Rails best practices_slides

Memoizationone is better than the other

value is not stored if false or nil is returned

||=

extend ActiveSupport::Memoizablememoize :recent_tweet, :followers_tweet, ...

def expensive(num) # lots of processingend

memoize :expensive

expensive(2)expensive(4)

expensive(2)expensive(4)

loaded from cache

LEVEL 2 controller command51

Page 52: Rails best practices_slides

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 command52

Page 53: Rails best practices_slides

/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 command53

Page 54: Rails best practices_slides

/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 command54

Page 55: Rails best practices_slides

level 3

Model Mayhem

55

Page 56: Rails best practices_slides

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 endend

56

Page 57: Rails best practices_slides

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

endend

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

Page 58: Rails best practices_slides

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

Page 59: Rails best practices_slides

LEVEL 3 Model mayhem59

Page 60: Rails best practices_slides

LEVEL 3 Model mayhem60

Page 61: Rails best practices_slides

LEVEL 3 Model mayhem

$ curl -d "user[login]=hacked&user[is_admin]=true&user[password]=password&user[password_confirmation]=password&user[email][email protected]" http://url_not_shown/users

user[is_admin]=true

61

Page 62: Rails best practices_slides

protecting your attributes/app/models/user.rb

class User < ActiveRecord::Base attr_protected :is_adminend

/app/models/user.rb

class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmationend

whitelists are better for security

LEVEL 3 Model mayhem62

Page 63: Rails best practices_slides

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 mayhem63

Page 64: Rails best practices_slides

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 endend

/db/migrate/20110119150620_add_default_5me_zone_account_se7ngs.rb

LEVEL 3 Model mayhem64

Page 65: Rails best practices_slides

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

Page 66: Rails best practices_slides

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

Page 67: Rails best practices_slides

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::BaseTRENDING_PERIOD =

TRENDING_PERIOD

67

Page 68: Rails best practices_slides

Proper use of callbacks

LEVEL 3 Model mayhem

Crea;ng  an  object

before_validationafter_validationbefore_saveafter_savebefore_createaround_createafter_create

Upda;ng  an  object Dele;ng  an  object

before_validationafter_validationbefore_saveafter_savebefore_updatearound_updateafter_update

before_destroyafter_destroyaround_destroy

68

Page 69: Rails best practices_slides

Rails date helpers

LEVEL 3 Model mayhem

Date  Helpers

1.minute2.hour3.days4.week5.months6.year

Modifiers More  Modifiers

beginning_of_daybeginning_of_weekbeginning_of_monthbeginning_of_quarterbeginning_of_year

2.weeks.ago3.weeks.from_now

next_weeknext_monthnext_year

* singular or plural

* end can be used

* prev can be used

69

Page 70: Rails best practices_slides

Proper use of callbacks

LEVEL 3 Model mayhem70

Page 71: Rails best practices_slides

Proper use of callbacks

LEVEL 3 Model mayhem

/app/models/following.rb

class Following < ActiveRecord::Base after_create :send_follower_notification

def send_follower_notificationifqueue_new_follower_email

endend

end

self.followed_user.receive_emails?

71

Page 72: Rails best practices_slides

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

endend

:if => :followed_can_receive_emails?queue_new_follower_email,

72

Page 73: Rails best practices_slides

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

Page 74: Rails best practices_slides

improved validation

LEVEL 3 Model mayhem

.errors.add(:name, 'is inappropriate')selfunless ContentModerator.is_suitable?( .name)

selfend

/app/models/topic.rb

end

end

def validate

class Topic < ActiveRecord::Base

74

Page 75: Rails best practices_slides

improved validation

LEVEL 3 Model mayhem

.errors.add(:name, 'is inappropriate')

/app/models/topic.rb

end

end

class Topic < ActiveRecord::Basevalidate :appropriate_content

private

def appropriate_contentselfunless ContentModerator.is_suitable?( .name)

selfend

75

Page 76: Rails best practices_slides

improved validation

LEVEL 3 Model mayhem

/app/models/topic.rb

endend

class Topic < ActiveRecord::Basevalidates :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)

endrecord attribute

Don’t forget to require this/lib  isn’t  auto-­‐loaded  by  default

76

Page 77: Rails best practices_slides

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 endend

Topic.create(Topic.create(Topic.create(

)):name => "Ruby5", :mentions => 2312

:name => "Top Ruby Jobs", :mentions => 231:name => "Rails for Zombies", :mentions => 1023)

77

Page 78: Rails best practices_slides

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 :mentionsend

/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

Page 79: Rails best practices_slides

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] endend

79

Page 80: Rails best practices_slides

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] endend

80

Page 81: Rails best practices_slides

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]

endend

t.save!

81

Page 82: Rails best practices_slides

Level 4Model Bert

82

Page 83: Rails best practices_slides

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 endend

=> "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 Bert83

Page 84: Rails best practices_slides

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 endend

.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/bulletBullet gem

To find all your n+1 queries

LEVEL 4 Model Bert84

Page 85: Rails best practices_slides

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 ReTweets1 ReTweets0 ReTweets

Bad English

LEVEL 4 Model Bert85

Page 86: Rails best practices_slides

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 ReTweets1 ReTweet0 ReTweets

LEVEL 4 Model Bert86

Page 87: Rails best practices_slides

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=X2. Populate an array of tweet objects3. Call length on that array

For Each tweetLots of unneeded objects

pluralize(

LEVEL 4 Model Bert87

Page 88: Rails best practices_slides

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=X2. do a count query for retweets

For Each tweet

possibly 10+ count queriesLEVEL 4 Model Bert

88

Page 89: Rails best practices_slides

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=XFor Each tweet

There is no step 2

with  counter_cache

89

Page 90: Rails best practices_slides

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_idend

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 Bert90

Page 91: Rails best practices_slides

counter_cache Money

true:counter_cache =>

/app/models/tweet.rb

has_many :retweets, :class_name => 'Tweet', :foreign_key => :tweet_idend

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 Bert91

Page 92: Rails best practices_slides

counter_cache Money

:counter_cache =>

/app/models/tweet.rb

has_many :retweets, :class_name => 'Tweet', :foreign_key => :tweet_idend

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 Bert92

Page 93: Rails best practices_slides

counter_cache Money

t.retweets.length pull  all  recordsthen  calls  .length

pull  all  recordsthen  calls  .length

t.retweets.count count  query count  query

t.retweets.size count  queryno  query

look  at  cache

Without Cache Counter With Cache Counter

LEVEL 4 Model Bert93

Page 94: Rails best practices_slides

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 Bert94

Page 95: Rails best practices_slides

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 Bert95

Page 96: Rails best practices_slides

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 Bert96

Page 97: Rails best practices_slides

Law of DemeterEach unit should have limited knowledge about other units

“don’t talk to strangers”

tweet user account settings

XLEVEL 4 Model Bert

97

Page 98: Rails best practices_slides

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 endend

LEVEL 4 Model Bert98

Page 99: Rails best practices_slides

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 endend

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 MethodsLEVEL 4 Model Bert

99

Page 100: Rails best practices_slides

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 settingsX

self.user.location_on_tweets

ERROR!! account_setting is nil!

LEVEL 4 Model Bert100

Page 101: Rails best practices_slides

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 settingsX

self.user.location_on_tweets

:allow_nil => true

,,

Returns nil when Account_Settings is missing

LEVEL 4 Model Bert101

Page 102: Rails best practices_slides

Head to to_s /app/models/user.rb

<%= @user.display_name %>

class User < ActiveRecord::Base def display_name "#{first_name} #{last_name}" endend

LEVEL 4 Model Bert102

Page 103: Rails best practices_slides

Head to to_s /app/models/user.rb

<%= @user %>

class User < ActiveRecord::Base def to_s "#{first_name} #{last_name}" endend

LEVEL 4 Model Bert103

Page 104: Rails best practices_slides

to_param-alama ding dong

/app/models/topic.rb

/post/2133

class Topic < ActiveRecord::Base def to_param "#{id}-#{name.parameterize}" endend

/post/rails-best-practices

SEO Friendly URLS

/post/2133-rails-best-practices

LEVEL 4 Model Bert104

Page 105: Rails best practices_slides

to_param-alama ding dong/app/models/topic.rb

class Topic < ActiveRecord::Base def to_param "#{id}-#{name.parameterize}" endend

/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 Bert105

Page 106: Rails best practices_slides

Level 5Froggy Views

106

Page 107: Rails best practices_slides

The example

LEVEL 5 Froggy Views107

Page 108: Rails best practices_slides

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

Page 109: Rails best practices_slides

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

Page 110: Rails best practices_slides

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

Page 111: Rails best practices_slides

LEVEL 5 Froggy Views

/app/views/tweets/index.html.erb

@followers_count @recent_followers@following_count @recent_following

<%= follow_box(<%= follow_box(

"Followers", , ) %>"Following", , ) %>FollowersFollowing

/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

Page 112: Rails best practices_slides

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

Page 113: Rails best practices_slides

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.downcasetitle count

recent.each do |user|str +=

user.avatar.url(:thumb)link_to user do

image_tag(end

)

)

content_tag :div, :class => dostr = )

end

raw(str

+ content_tag(:span,

Annoying str variable

/app/helpers/tweets_helper.rb

Helper Skelter

113

Page 114: Rails best practices_slides

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

titlecount

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

Page 115: Rails best practices_slides

The Example

LEVEL 5 Froggy Views115

Page 116: Rails best practices_slides

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

Page 117: Rails best practices_slides

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

Page 118: Rails best practices_slides

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

Page 119: Rails best practices_slides

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

Page 120: Rails best practices_slides

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

Page 121: Rails best practices_slides

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

Page 122: Rails best practices_slides

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

Page 123: Rails best practices_slides

LEVEL 5 Froggy Views

empty string things

<% if @user.email.blank? %>

<% unless @user.email? %>

<% if @user.email.present? %>

<% if @user.email? %>

123

Page 124: Rails best practices_slides

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

Page 125: Rails best practices_slides

LEVEL 5 Froggy Views

empty string things

<%= @user.city || @user.state || "Unknown" %>

<%= @user.city.presence || @user.state.presence || "Unknown" %>

125

Page 126: Rails best practices_slides

LEVEL 5 Froggy Views

empty string things

city is nil

undefined method `titleize' for nil:NilClass

<%= @user.city.titleize %>|| "Unknown"

126

Page 127: Rails best practices_slides

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

Page 128: Rails best practices_slides

The example

LEVEL 5 Froggy Views128

Page 129: Rails best practices_slides

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

Page 130: Rails best practices_slides

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 endend

, :class =>

id="tweet_<%= tweet.id %>"

130

Page 131: Rails best practices_slides

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

Page 132: Rails best practices_slides

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

Page 133: Rails best practices_slides

LEVEL 5 Froggy Views

/app/controllers/tweets_controller.rb

what if all actions in the tweet controller need the sidebar?

class TweetsController < ApplicationControllerlayout '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

Page 134: Rails best practices_slides

LEVEL 5 Froggy Views

The example

134

Page 135: Rails best practices_slides

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(",") endend

/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

Page 136: Rails best practices_slides

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

Page 137: Rails best practices_slides

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