天天看點

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

版權聲明:本文為CSDN部落客「weixin_34006965」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/weixin_34006965/article/details/91632614