Skip to content

Instantly share code, notes, and snippets.

@iannono
Last active December 22, 2015 15:19
Show Gist options
  • Save iannono/6491827 to your computer and use it in GitHub Desktop.
Save iannono/6491827 to your computer and use it in GitHub Desktop.
Notes of rails best practice of codeschool
  • use scope
scope :trending, -> { |num = nil| where('started_trending > ?', 1.days.ago).
                                  order('mentions desc').
                                  limit(num) }
                                  
@tweets = current_user.tweets.unscoped.order(:status).limit(10)
  • use filter
before_filter :auth, :only => [:edit, :update, :destroy]
                     :except => [:index, :create]

skip_before_filter :require_login, :only => [:new, :create]
  • use active_model to extract your non database model
class ContactForm
  include ActiveModel::Validations
  include ActiveModel::Conversion
  
  attr_accessor :name, :email, :body
  validates_presence_of :name, :email, :body
  
  def initialize(attributes = {})
    attributes.each do |name, value|
      send("#{name}=", value) 
    end
  end
  
  def persisted?
    false
  end
  • use nested attributes
  1. use accepts_nested_attributes_for :account_setting in your model
  2. use <%= fields_for :account_setting do |a| %> in your view
  3. use @user = User.new(:account_setting => AccountSetting.new) in your controller
  • use presenters to extract your view logic
#bad code
def index
  @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)
  ...
end

#good code
def index
  @presenter = Tweets::IndexPresenter.new(current_user)
end

class Tweets::IndexPresenter
  extend ActiveSupport::Memoizable
  
  def initializer(user)
    @user = user
  end
  
  def followers_tweets
    @user.follower_tweets.limit(20)
  end
  
  def recent_tweet
    @user.tweets.first
  end  
  
  ...
  memoize :recent_tweet, :followers_tweets
end

#now in the view
<%= @presenter.recent_tweet.body %>
<%= @presenter.recent_tweet.created_at %>
#and because of memoize, this will only generate one tweet object
  • 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))
  • use whitelists
class User < ActiveRecord::Base
  attr_accessible :email, :password
end
  • proper use of callbacks
class Topic < ActiveRecord::Base
  TRENDING_PERIOD = 1.week
  
  before_create :set_trend_ending
  
  after_create :queue_new_topic_user, :if => Proc.new { |t| t.user.name.match /xiongbo/ }
  
  private
  def set_trend_ending
    self.finish_trending = TRENDING_PERIOD.from_now
  end
end

improved validation

class Topic < ActiveRecord::Base
  validates :name, :appropriate => true
end

#/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
  • use size with counter_cache <%= 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
  • use find_each for running query
desc 'Task involving all tweets'
task :tweet_task => :environment do

  #default batch_size is 100
  Tweet.find_each(:batch_size => 200) do |tweet|
    p "task for #{tweet}"
  end
end
  • each unit should have limited knowledge about other units which we should use delegate
class Tweet < ActiveRecord::Base
  def location_data
    #bad: when user change location logic on other object
    if self.user.account_setting.location_on_tweets
      self.location
    else
      "unavailable"
    end
    
    #good
    if self.user.location_on_tweets
    ...
  end
end

class User < ActiveRecord::Base
  has_one :account_setting, :dependent => :destroy
  
  delegate :location_on_tweets, :public_email, :to => :account_setting, :allow_nil => true
end
  • use to_s for model's default output and use to_param for fridndly urls
def to_s
  "#{first_name} #{last_name}"
end
#use for <%= @user %>

def to_param
  "#{id}-#{name.parameterize}"
end
#use for <%= link to topic.name, topic %> #=>/post/21-rails-best-practices
  • use html helper to generate html content for helper_methods
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

# then in the view
<%= follow_box("Followers", @followers_count, @recent_followers) %>
  • counter_cache
class Favorite < ActiveRecord::Base
  belongs_to :tweet,
             :counter_cache => :favorites_count
end

class AddCounterCacheColumnToTweets < ActiveRecord::Migration
  def self.up
    # add column here
    add_column :tweets, :favorites_count, :integer
  end

  def self.down
    # remove column here
    remove_column :tweets, :favorites_count
  end
end

# @tweet.favorites.create() will cause an insert and an update
# update "tweets" set "favorites_count" = "favorited_count" + 1 where ("tweets"."id" = 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment