构造联合查询

def self.search(title, category_id, city_id)
    rel = scoped
    rel = rel.where('title    like ?', "%#{title}%") if(title)
    rel = rel.where('category_id = ?', category_id)  if(category_id)
    rel = rel.where('city_id     = ?', city_id)      if(city_id)
    rel
end

可用的回调

Creating an Object

before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit/after_rollback

Updating an Object

before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit/after_rollback

Destroying an Object

before_destroy
around_destroy
after_destroy
after_commit/after_rollback

调用save方法时的callback顺序

before_validation
before_validation :on => :create
after_validation
after_validation  :on => :create
before_save
before_create
after_create
after_save

callback方法之间的一些差别

  • 参考: http://stackoverflow.com/questions/6422199/what-is-the-difference-between-after-create-and-after-save-and-when-to-use-w
  • after_create only works once - just after the record is first created.
  • after_save works every time you save the object - even if you’re just updating it many years later

Generator 例子

bin/rails g model pa/bet --migration=false --fixture-replacement=fabrication
bin/rails g model pa/prebet --migration=false --fixture-replacement=fabrication
bin/rails g controller pa/term_notices --no-assets
bin/rails g controller SlsChannels --no-assets
bin/rails g model LotFarm --no-fixture --no-migration
bin/rails g model sls/activity_verify_charge --no-fixture --no-migration

自定义config

# config/initializers/load_config.rb
APP_CONFIG = YAML.load_file("#{Rails.root}/config/config.yml")[Rails.env]

# application.rb
if APP_CONFIG['perform_authentication']
  # Do stuff
end

Tabel.column migrations

change_table :table do |t|
  t.column
  t.index
  t.timestamps
  t.change
  t.change_default
  t.rename
  t.references
  t.belongs_to
  t.string
  t.text
  t.integer
  t.float
  t.decimal
  t.datetime
  t.timestamp
  t.time
  t.date
  t.binary
  t.boolean
  t.remove
  t.remove_references
  t.remove_belongs_to
  t.remove_index
  t.remove_timestamps
end

ActiveModel::Dirty

  • 参考:http://stackoverflow.com/questions/5501334/how-to-properly-handle-changed-att
person = Person.find_by_name('Uncle Bob')
person.changed?       # => false
person.name = 'Bob'
person.changed?       # => true
person.name_changed?  # => true
person.name_was       # => 'Uncle Bob'
person.name_change    # => ['Uncle Bob', 'Bob']

Whenever

whenever --set environment=development -i
whenever --set environment=staging -i

时区 Time Zone

rake time:zones:all
config.time_zone = 'Beijing'
config.active_record.default_timezone = :local
Rails stores at +0000 Time zone by default

HTML标签辅助方法 helper method

select

select '', 'lot_name', ['重庆时时彩', '双色球'], include_blank: false
select '', 'terminal_no', [['任意', nil]\]

options_for_contest_itype = [['photo', 0], ['video', 1], ['word', 2], ['audio', 4]]
select 'contest', 'itype', options_for_contest_itype, selected: @contest.itype

select '', 'contest[sponsoring[][sponsor]]', options_for_contest_sponsors, selected: sponsoring.sponsor.id

radio button

radio_button '', 'u_depoted', true, checked: @pre_term.depoted

<%= radio_button 'contest', 'use_default_rule', true, checked: @preview_contest.use_default_rule, style: 'display:none;', class: 't' %>
<%= radio_button 'contest', 'use_default_rule', false, checked: !@preview_contest.use_default_rule, style: 'display:none;', class: 'f' %>

check box

<%= check_box_tag 'contest[use_default_rule]', @preview_contest.use_default_rule, @preview_contest.use_default_rule, style: 'display:none;' %>

API 设计

Versioned Controller

Applications.routes.draw do
  namespace :v1 do
    resources :orders # V1::OrdersController, /v1/orders
  end
  namespace :v2 do
    resources :orders # V2::OrdersController, /v2/orders
  end
end

Application.router.draw do
  constraints(:version => 1) do # namespace V1
    resources :orders # V1::OrdersController, /orders
  end
  constraints(:version => 2)  do # namespace V2
    resources :orders # V2::OrdersController, /orders
  end
end

cycle helper 单双循环

<tr class=<%= cycle('even', 'odd') %>>

HTTP Basic Auth

class Admin::ApplicationController < ApplicationController

  USER_NAME, PASSWORD = "zhou", "111111"

  before_filter :admin_authenticate


  private

  def admin_authenticate
    authenticate_or_request_with_http_basic do |user_name, password|
      user_name == USER_NAME && password == PASSWORD
    end
  end

end

Rake任务在生产环境输出日志

Rails.logger.auto_flushing = true

Rake migrate task

$ rake db:version 查看迁移版本
# 恢复迁移(我需要修改某个历史的migration文件的迁移逻辑,比如20080930121212)
$ rake db:migrate:down VERSION=20080930121212
$ rake db:migrate

Helper Method

#application_controller.rb
def current_user
  @current_user ||= User.find_by_id!(session[:user_id])
end
helper_method :current_user

Asset Path Helper

.css.erb
background-image:url(<%=asset_path "admin/logo.png"%>);
- 实例
首先将美工给的common.css文件改名为common.css.erb
然后利用asset_path来引入image
.header{width:960px;height:75px;background:url(<%= asset_path 'main_bg.png' %>) 0 0 repeat-x;overflow:hidden;}

Mime Types

"*/*"                      => :all
"text/plain"               => :text
"text/html"                => :html
"application/xhtml+xml"    => :html
"text/javascript"          => :js
"application/javascript"   => :js
"application/x-javascript" => :js
"text/calendar"            => :ics
"text/csv"                 => :csv
"application/xml"          => :xml
"text/xml"                 => :xml
"application/x-xml"        => :xml
"text/yaml"                => :yaml
"application/x-yaml"       => :yaml
"application/rss+xml"      => :rss
"application/atom+xml"     => :atom
"application/json"         => :json
"text/x-json"              => :json

ORM Associations 关系

Singular associations (one-to-one)


                                  |            |  belongs_to  |
generated methods                 | belongs_to | :polymorphic | has_one
----------------------------------+------------+--------------+---------
other                             |     X      |      X       |    X
other=(other)                     |     X      |      X       |    X
build_other(attributes={})        |     X      |              |    X
create_other(attributes={})       |     X      |              |    X
create_other!(attributes={})      |     X      |              |    X

Collection associations (one-to-many / many-to-many)

                                  |       |          | has_many
generated methods                 | habtm | has_many | :through
----------------------------------+-------+----------+----------
others                            |   X   |    X     |    X
others=(other,other,...)          |   X   |    X     |    X
other_ids                         |   X   |    X     |    X
other_ids=(id,id,...)             |   X   |    X     |    X
others<<                          |   X   |    X     |    X
others.push                       |   X   |    X     |    X
others.concat                     |   X   |    X     |    X
others.build(attributes={})       |   X   |    X     |    X
others.create(attributes={})      |   X   |    X     |    X
others.create!(attributes={})     |   X   |    X     |    X
others.size                       |   X   |    X     |    X
others.length                     |   X   |    X     |    X
others.count                      |   X   |    X     |    X
others.sum(*args)                 |   X   |    X     |    X
others.empty?                     |   X   |    X     |    X
others.clear                      |   X   |    X     |    X
others.delete(other,other,...)    |   X   |    X     |    X
others.delete_all                 |   X   |    X     |    X
others.destroy(other,other,...)   |   X   |    X     |    X
others.destroy_all                |   X   |    X     |    X
others.find(*args)                |   X   |    X     |    X
others.exists?                    |   X   |    X     |    X
others.distinct                   |   X   |    X     |    X
others.uniq                       |   X   |    X     |    X
others.reset                      |   X   |    X     |    X

Validate Errors

# ActiveRecord对象的错误模型类似下面:

user.errors #=>
#<ActiveModel::Errors:0x007f8fe8288370
 @base=
  #<User id: nil, name: nil, birthday: nil, gender: nil, mobile: nil, email: nil, avatar: nil, password_hash: nil, password_salt: nil, sn: nil, status: nil, level: 1, point_count: 0, vote_count: 0, created_at: nil, updated_at: nil, marry: 0, location: nil, role: "user", created_ref: 0>,
 @messages=
  {:email=>["can't be blank", "has already been taken"],
   :mobile=>["can't be blank", "has already been taken"]}>


user.errors.messages #=>
{:email=>["can't be blank", "has already been taken"],
 :mobile=>["can't be blank", "has already been taken"]}

user.errors.sort  #=>
[[:email, "can't be blank"],
 [:email, "has already been taken"],
 [:mobile, "can't be blank"],
 [:mobile, "has already been taken"]]

user.errors.to_a #=>
["Email can't be blank",
 "Email has already been taken",
 "Mobile can't be blank",
 "Mobile has already been taken"]

user.errors.keys #=>
[:email, :mobile]

用户,关注,粉丝的三者关系的在activerecord中的快速实现

表结构

follows
Column       | Type     | Modifiers | Comment
------------ | -------- | --------- | --------
following_id | integer  |           |  被关注者的id
follower_id  | integer  |           |  关注者的id
created_at   | datetime |           |
updated_at   | datetime |           |

users
Column        | Type     | Modifiers    | Comment
------------- | -------- | ------------ |------------------
id            | integer  |              |
sn            | string   |              |
gender        | integer  |              |  性别, 0 女, 1 男

模型

class Follow < ActiveRecord::Base
  belongs_to :following, class_name: 'User'
  belongs_to :follower, class_name: 'User'
end

class User < ActivieRecord::Base

  # 有许多偶像
  has_many :following_relationships, class_name: 'Follow', foreign_key: 'follower_id'
  has_many :followings, through: :following_relationships

  # 有许多粉丝
  has_many :follower_relationships, class_name: 'Follow', foreign_key: 'following_id'
  has_many :followers, through: :follower_relationships

end

获取 user agent

request.env["HTTP_USER_AGENT"]
#or
request.user_agent

Test Assert

 assert( test, [msg] ), Ensures that test is true
 assert_not( test, [msg] ), Ensures that test is false.
 assert_equal( expected, actual, [msg] ), Ensures that expected == actual is true.
 assert_raises( exception1, exception2, ... ) { block }, Ensures that the given block raises one of the given exceptions.

    assert_raise NoEnoughVoteOrCreditError do
      credit_log = FchkCredits::Vote << {user: user, entity: entity}
    end

文件上传 carrierwave

$ spring rails generate uploader RuleFile  #=> create app/uploaders/rule_file_uploader.rb
$ spring rails g migration add_rule_file_to_contests
$ rake db:migrate
class Contest < ActiveRecord::Base
  mount_uploader :rule_file, RuleFileUploader
end

contest = Contest.new
contest.rule_file = params[:rule_file]
contest.save!
contest.rule_file.url
contest.rule_file.current_path
contest.rule_file.identifier

解析客户端gzip压缩数据

  ActiveSupport::Gzip.decompress(request.raw_post)

http status code

Response Class	HTTP Status Code	Symbol
Informational	100	:continue
101	:switching_protocols
102	:processing
Success	200	:ok
201	:created
202	:accepted
203	:non_authoritative_information
204	:no_content
205	:reset_content
206	:partial_content
207	:multi_status
208	:already_reported
226	:im_used
Redirection	300	:multiple_choices
301	:moved_permanently
302	:found
303	:see_other
304	:not_modified
305	:use_proxy
306	:reserved
307	:temporary_redirect
308	:permanent_redirect
Client Error	400	:bad_request
401	:unauthorized
402	:payment_required
403	:forbidden
404	:not_found
405	:method_not_allowed
406	:not_acceptable
407	:proxy_authentication_required
408	:request_timeout
409	:conflict
410	:gone
411	:length_required
412	:precondition_failed
413	:request_entity_too_large
414	:request_uri_too_long
415	:unsupported_media_type
416	:requested_range_not_satisfiable
417	:expectation_failed
422	:unprocessable_entity
423	:locked
424	:failed_dependency
426	:upgrade_required
423	:precondition_required
424	:too_many_requests
426	:request_header_fields_too_large
Server Error	500	:internal_server_error
501	:not_implemented
502	:bad_gateway
503	:service_unavailable
504	:gateway_timeout
505	:http_version_not_supported
506	:variant_also_negotiates
507	:insufficient_storage
508	:loop_detected
510	:not_extended
511	:network_authentication_required

log filter params 日志过滤敏感参数

  • 参考: http://stackoverflow.com/questions/7232554/how-to-filter-parameters-in-rails
config.filter_parameters += [:password, :password_confirmation, :credit_card]

filters = Rails.application.config.filter_parameters
f = ActionDispatch::Http::ParameterFilter.new filters
f.filter :password => 'haha' # => {:password=>"[FILTERED]"}

在控制器里实施


# app/controllers/api/base_api_controller.rb
class Api::BaseApiController < ApplicationController
  filter_parameter_logging :password, :password_confirmation, :card_number
end

# app/controllers/mobile/mobile_api_controller.rb
class Mobile::BaseMobileController < ApplicationController
  filter_parameter_logging :password, :password_confirmation
end

Console 里调用 view helpers 方法

  • 参考: http://stackoverflow.com/questions/151030/how-do-i-call-controller-view-methods-from-the-console-in-rails
helper.number_to_currency('123.45')
helper.send :h, '<script>alert("see you")</script>'

样例: 使用 SQL 创建一个带 id 的 表

需要这样声明 id: id serial primary key

create table batch_order_invoice_tasks(
  id serial primary key,
  total_invoice_count integer NOT NULL,
  completed_invoice_count integer NOT NULL default 0,
  user_id integer NOT NULL,
  name character varying(255),
  state character varying(255),
  created_at timestamp without time zone,
  updated_at timestamp without time zone
)

ActiveRecord 优化 tips

  • http://hashrocket.com/blog/posts/rails-quick-tips-easy-activerecord-optimizations

blank? VS empty? 使用 empty?

# Using `blank?`
User.where(screen_name: ['user1','user2']).blank?

# 1. Queries database for all user data
#   SELECT "users".* FROM "users" WHERE "users"."screen_name" IN ('user1','user2')

# 2. Loads users into an array
#   [<#User:0x007fbf6413c510>,<#User:0x007fbf65ab1c70>]

# 3. Checks to see if the array size is zero
#   => true
# Using `empty?`
User.where(screen_name: ['user1','user2').empty?

# 1. Queries database for ONLY a count
#   SELECT COUNT(*) FROM "users" WHERE "users"."screen_name" IN ('user1','user2')

# 2. Checks to see if the count is zero
# => true

map? VS pluck? 使用 pluck?

# Using `map?`
User.where(email: ['jane@example.com', 'john@example.com']).map(&:screen_name)

# 1. Queries database for all user data
#   SELECT "users".* FROM "users" WHERE "users"."email" IN ('jane@example.com','john@example.com')

# 2. Loads users into an array
#   [<#User:0x007fbf6413c510>,<#User:0x007fbf65ab1c70>]

# 3. Iterates over users to collect screen_names
#   ['user1','user2']
# Using `pluck?`
User.where(email: ['jane@example.com', 'john@example.com']).pluck(:screen_name)

# 1. Queries database for only screen_names
#   SELECT "users"."screen_name" FROM "users" WHERE "users"."email" IN ('jane@example.com','john@example.com')

# 2. Returns those screen_names in an array
#   ['user1','user2']
User.where(screen_name: users.select(:screen_name)).empty?