rails最佳实践 --codeschool笔记

rails最佳实践
--codeschool笔记

FAT MODEL, SKINNY CONTROLLER
  代码争取放在model中,因为model是功能模块,更易于测试,而且更底层,易于复用。
  controller是业务逻辑,对错后知后觉.而且想想把业务逻辑和功能都放在一起,那是多么杯具的事件。
Scope it out
  bad_code:
  XX_controller:
    @tweets = Tweet.find(
      :all,
      :conditions => {:user_id => current_user.id},
      :order => 'created_at desc',
      :limit => 10
      )
  好一点,但还是行:
    @tweets = current_user.tweets.order('created_at desc').limit(10)
  good_code:
    tweets_controller.rb
      @tweets = current_user.tweets.recent.limit(10)
    models/tweet.rb
      scope :recent, order('created_at desc' )
  这个重构的原则就是不要在controller里出现与实现功能相关的代码如created_at desc ,这个代码属于功能性代码,而recent这个scope则能很好的表达order('created_at desc' )这个功能组合 。
    使用:default_scope  个人认为这个不是很好,因为default这种有辐射性质的函数很难控制
    使用lambda{}
    scope :trending, lambda { where('started_trending > ?', 1.day.ago ).order( 'mentions desc' ) }
    上面会查询一次,后面再用是取值,把前面取到的词返回,加lambda解决这个问题
  lambda { |var = nil| xxxx } 使用默认值:
    scope :trending, lambda { |num = nil| where('started_trending > ?', 1.day.ago ).order( 'mentions desc' ).limit(num) }
    @trending = Topic.trending(5)
    @trending = Topic.trending
  unscoped:  
    default_scope order ('created_at desc' ) 
    @tweets = current_user.tweets.unscoped.order(:status).limit(10)
  tweets_controller.rb
    BAD:
      t = Tweet.new
      t.status = "RT #{@tweet.user.name}: #{@tweet.status}"
      t.original_tweet = @tweet
      t.user = current_user
      t.save
    GOOD:
      current_user.tweets.create(
        :status => "RT #{@tweet.user.name}: #{@tweet.status}",
        :original_tweet => @tweet
        )
fantastic filters
  BAD
    before_filter :get_tweet, :only => [:edit, :update, :destroy]
  GOOD:
    @tweet = get_tweet( params[:id])
    private
    def get_tweet (tweet_id)
      Tweet.find(tweet_id)
    end
  :except => [:index, :create]

Nested attributes
  @user = User.new(params[:user])
  @user.save

  has_one :account_setting, :dependent => :destroy
  accepts_nested_attributes_for :account_setting

  <%= form_for(@user) do |f| %>
    ...
    <%= f. fields_for :account_setting do |a| %>
  def new
    @user = User.new (:account_setting => AccountSetting.new)
  end
Models without the database
  class ContactForm
    include ActiveModel::Validations
    include ActiveModel::Conversion #<%= form_for @contact_form
    
    attr_accessor :name, :email, :body
    validates_presence_of :name, :email, :body
    
    def initialize(attributes = {})
      attributes.each do |name, value|
        send("#{name}=", value)       #ContactForm.new(params[:contact_form])
      end
    end
    
    def persisted?
      false
    end
  end

  <%= form_for @contact_form , :url => send_email_path do |f| %>

  @contact_form = ContactForm.new
  def new
    @contact_form = ContactForm.new
  end
  def send_email
    @contact_form = ContactForm.new(params[:contact_form])
    if @contact_form.valid?
      Notifications.contact_us( @contact_form ).deliver
      redirect_to root_path, :notice => "Email sent, we'll get back to you"
    else
      render :new
    end
  end

really Rest
  UsersController
    subscribe_mailing_list
    unsubscribe_mailing_list
  SubscriptionsController
    create
    destroy

Enter the Presenters
  @presenter = Tweets::IndexPresenter.new(current_user)
  /conditionsnfig/application.rb
    config.autoload_paths += [config.root.join("app/presenters")]
  /app/presenters/tweets/index_presenter.rb
    class Tweets::IndexPresenter
      extend ActiveSupport::Memoizable

      def initialize(user)
        @user = user
      end

      def followers_tweets
        @user.followers_tweets.limit(20)
      end

      def recent_tweet
        @recent_tweet ||= @user.tweets.first
      end

      def trends
        @user.trend_option == "worldwide"
        if trends
          Trend.worldwide.by_promoted.limit(10)
        else
          Trend.filter_by(@user.trend_option).limit(10)
        end
      end

      memoize :recent_tweet, :followers_tweet, ...
    end

Memoization

  extend ActiveSupport::Memoizable
  memoize :recent_tweet, :followers_tweet, 
  def expensive(num)
  # lots of processing
  end
  memoize :expensive
  expensive(2)
  expensive(2)

reject sql injection
  
  BAD:
    User.where("name = #{params[:name]}")
  GOOD:
    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))

Rails 3 responder syntax
  
  respond_to :html, :xml, :json
  def index
    @users = User.all
    respond_with(@users)
  end
  def show
    @user = User.find(params[:id])
    respond_with(@user)
  end

Loving your indices  #index索引的复数
  经常desc的属性,可以加index,index就是用插入时间换查询时间

protecting your attributes
  bad:
    attr_protected :is_admin
  good:
    attr_accessible :email, :password, :password_confirmation

default values
  
  change_column_default :account_settings, :time_zone, 'EST'
  change_column :account_settings, :time_zone, :string, nil

Proper use of callbacks
  
  RENDING_PERIOD = 1.week
  before_create :set_trend_ending
  private
  def set_trend_ending
    self.finish_trending = TRENDING_PERIOD .from_now
  end

Rails date helpers
  Date Helpers:
    1.minute
    2.hour
    3.days
    4.week
    5.months
    6.year
  Modifiers:
    beginning_of_day      #end
    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

improved validation
  /lib/appropriate_validator.rb
  class AppropriateValidator < ActiveRecord::EachValidator
    def validate_each(record, attribute, value)
      unless ContentModerator.is_suitable?(value)
        record.errors.add( attribute, 'is inappropriate')
      end
    end
  end
  /app/models/topic.rb
  validates :name, :appropriate => true

Sowing the Seeds
  topics =[ {:name=> "Rails for Zombies", :mentions => 1023},
            { :name=> "Top Ruby Jobs", :mentions => 231},
            {:name=> "Ruby5", :mentions => 2312}]
  Topic.destroy_all

  Topic.create do |t|
    t.name = attributes[:name]
    t.mentions = attributes[:mentions]
  end
  不够好
  topics.each do |attributes|
    Topic.find_or_initialize_by_name( attributes[:name]).tap do |t|
      t.mentions = attributes[:mentions]
      t.save!
    end
  end

N+1 is not for fun
  self.followers.recent.collect{ |f| f.user.name }.to_sentence
  self.followers.recent.includes(:user).collect{ |f| f.user.name }.to_sentence
    Select followers where user_id=1
    Select users where user_id in (2,3,4,5)

Bullet gem
  https://github.com/flyerhzm/bullet
  To find all your n+1 queries

counter_cache Money
  pluralize( tweet.retweets.length , "ReTweet")   
  pluralize( tweet.retweets.count , "ReTweet")
  pluralize( tweet.retweets.size , "ReTweet")

  class Tweet < ActiveRecord::Base
    belongs_to :original_tweet,
                :class_name => 'Tweet',
                :foreign_key => :tweet_id,
                :counter_cache => : retweets_count

    has_many  :retweets,
                :class_name => 'Tweet',
                :foreign_key => :tweet_id
  end
  add_column :tweets,:retweets_count,:integer,:default => 0

Batches of find_each
  Tweet .find_each(:batch_size => 200) do |tweet|
    p "task for #{tweet}"
  end
  数量大的时候很有用 pulls batches of 1,000 at a time default

Law of Demeter
  delegate :location_on_tweets, :public_email,
            :to => :account_setting,
            :allow_nil => true
Head to to_s
  def to_s
    "#{first_name} #{last_name}"
  end

to_param-alama ding dong
  /post/2133
  /post/rails-best-practices
  /post/2133-rails-best-practices

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

  <%= link_to topic.name, topic %>
  /post/2133-rails-best-practices
  {:id => "2133-rails-best-practices"}
  Topic.find(params[:id])
  call to_i
  Topic.find(2133)

No queries in your view!
  
Helper Skelter
  index.html.erb
    <%= follow_box("Followers", @followers_count , @recent_followers) %>
    <%= follow_box("Following", @following_count , @recent_following) %>

    tweets_helper.rb
  def follow_box(title, count, recent)
    content_tag :div, :class => title.downcase do
      raw(
        title +
        content_tag(:span, count) +
        recent.collect do |user|
          link_to user do
            image_tag(user.avatar.url(:thumb))
        end
      end .join
      )
    end
  end


Partial sanity
  <%= render 'trending', :area => @user.trending_area,:topics => @trending %>
  <% topics.each do |topic| %>
  <li>
    <%= link_to topic.name, topic %>
    <% if topic.promoted? %>
      <%= link_to image_tag('promoted.jpg'), topic %>
    <% end %>
  </li>
  <% end %>

  <% topics.each do |topic| %>
    <%= render topic %>
  <% end %>

  <li>
    <%= link_to topic.name, topic %>
    <% if topic.promoted? %>
      <%= link_to image_tag('promoted.jpg'), topic %>
    <% end %>
  </li>

empty string things
  @user.email.blank?  @user.email.present? =  @user.email?
  <%= @user.city || @user.state || "Unknown" %>
  <%= @user.city.presence || @user.state.presence || "Unknown" %>
  <%= @user.city ? @user.city.titleize : "Unknown" %>
  <%= @user.city.try(:titleize) || "Unknown" %>

rock your block helpers
  <% @presenter.tweets.each do |tweet| %>
    <div id="tweet_<%= tweet.id %>"
      class="<%= 'favorite' if tweet.is_a_favorite?(current_user) %>">
    <%= tweet.status %>
    </div>
  <% end %>

  /app/views/tweets/index.html.erb
  <% @presenter.tweets.each do |tweet| %>
    <%= tweet_div_for(tweet, current_user) do %>
      <%= tweet.status %>
    <% end %>
  <% end %>
  /app/helpers/tweets_helper.rb
  def tweet_div_for(tweet, user, &block)
    klass = 'favorite' if tweet.is_a_favorite?(user)
    content_tag tweet, :class => klass do
      yield
    end
  end

Yield to the content_for
  <% if flash[:notice] %>
    <span style="color: green"><%= flash[:notice] %></span>
  <% end %>

  <%= yield :sidebar %>

  <% content_for(:sidebar) do %>
    ... html here ...
  <% end %>

  /app/views/layouts/applica5on.html.erb
  <%= yield :sidebar %>
    <% if flash[:notice] %>
      <span style="color: green"><%= flash[:notice] %></span>
    <% end %>
  <%= yield %>
  /app/controllers/tweets_controller.rb
  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' %>

meta Yield

/app/views/layouts/applica5on.html.erb
  <title>Twitter <%= yield(:title) %></title>
  <meta name="description"
    content="<%= yield(:description) || "The best way ..." %>">
  <meta name ="keywords"
    content="<%= yield(:keywords) || "social,tweets ..." %>">


/app/views/tweets/show.html.erb
  <%
    content_for(:title, @tweet.user.name)
    content_for(:description, @tweet.status)
    content_for(:keywords, @tweet.hash_tags.join(","))
  %>






















  

转载于:https://my.oschina.net/zhao/blog/52920

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值