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