Rails 指令
rails new my_app
: 新增 my_app 專案
rails s
: 啟動 server
rails s RAILS_ENV=development
: 啟動 development 環境的 server
rails s -b 0.0.0.0
: 開放外部, 預設只允許本機 (127.0.0.1:3000)
rvmsudo rails s -p 80
: 使用 80 port, 使用 rvmsudo
是因為 rails 起 80 port 一定要用 sudo 身份
rails s -p 5000
: 換 port, 預設是 3000
rails c
: rails console, 可以直接操作 ActiveRecord
rails c --sandbox
: 沙盒的 console, 在這期間改的 DB 內容在離開時都會還原
rails db
: 進入 db console 根據 config/database.yml
. ex: 如果是 sqlite, 就會進到 sqlite 的 console
rake stats
: 統計 code 寫了幾行
rake tmp:clear
: 清除 cache
console :
Rails.cache.clear
: 清除 cache
Routes
command
rake routes
: 查看所有routes
rake routes | grep user
: 查看所有 routes 含有 user
關鍵字的
觀念
4 HTTP methods, 4 URL helper, 7 actions
Helper GET POST PUT 或 PATCH DELETE
event_path(@event) /events/1 /events/1 /events/1
show update destroy
events_path /events /events
index create
edit_event_path(@event) /events/1/edit
edit
new_event_path /events/new
new
- show, edit, update, destroy 是單數, 對特定元素操作
- index, create 是複數, 對群集操作
基本 Route 語法
root 'welcome#index'
# 7 個 action 都使用
resources :info do
# 只允許 edit, update
resources :users, only: [:edit, :update]
# 除了 show 其他 action 都用
resources :products, except: [:show]
# 指定使用 PUT
put 'change_password', on: :member
end
# namespace 是 folder 名稱
namespace :dashboard do
# 原本 dashboard 在 route 的命名改為 admin, 通常是為了美化 route 或是減少不直覺的 url 造成的困惑才會使用
resources :welcome, as: 'home'
end
# 兩者是一樣的
resources :search, only: [:show]
get '/search/:id', to: 'search#show', as: 'search' # to: action, as: route name
Difference between member
and collection
如果要帶上原本物件的 id 就用 :member
如果只是需要一個一般的頁命就用 :collection
i.e.
resources :posts do
# on collection
get 'search', on: :collection # '/posts/search' and search_posts_path
# on member
get 'share', on: :member # '/posts/:id/share' and share_photo_path(@post)
end
member 與 collection 那一種寫法
resources :users do
member do
get :find
end
collection do
get :find
post :freeze
end
end
Url / Path 相關
完整 url
request.original_url
> http://127.0.0.1:3000/dashboard/admin/110/find_name
Path
request.path (= request.full.path)
> /dashboard/admin/110/find_name
判斷目前 path 是否一樣
current_page?(new_product_path)
> True / False
將 hash 轉為 query string
{ name: 'David', nationality: 'Danish' }.to_query
> name=David&nationality=Danish
在 Rails consloe 下
使用 route path 必須要先引入
include Rails.application.routes.url_helpers
> root_path
印出所有的 assets path
Rails.application.config.assets.paths
# 條列式
y Rails.application.config.assets.paths
ap Rails.application.config.assets.paths
path helper
xxxx_path(anchor: 'xx') # /xxxx#xx
xxxx_path(format: :json) # /xxxx.json
Dashboard 設計
config/route.rb :
root 'welcome#index' # 首頁
namespace :dashboard do
root 'welcome#index' # dashboard 的首頁
resources :musics, only: [:index]
end
controllers/dashboard_controller.rb
class DashboardController < ApplicationController
end
controllers/dashboard/welcome_controller.rb :
class Dashboard::WelcomeController < DashboardController
def index
end
end
views/dashboard/welcome/index.rb :
dashboard index
另一種寫法 dashboard_controller.rb
放在 controllers/dashboard 下
controllers/dashboard/dashboard_controller.rb
class Dashboard::DashboardController < ApplicationController
controllers/dashboard/welcome_controller.rb :
class Dashboard::WelcomeController < Dashboard::DashboardController
Controller
(最後更新 : 2016-04-27)
Params
全部通過
params.require(:post).permit!
只接收特定的欄位的參數
params[:user].permit(:name, ages)
# 或這樣寫
params.require(:user).permit(:name, :ages)
permit Array
params[:user].permit(user_contacts: [:name, :ages, phone])
Protect from forgery
Rails 會在 POST
, PUT
/PATCH
, DELETE
時檢查 authenticity token,
假如接收到假造的 token 如: {"authenticity_token"=>"g1mmeTH3brains=", "user"=>{"name"=>"Big Dummy"}}
則 protect_from_forgery 做出的反應會依照你在 controller 的設定, 有以下情況 :
- 擲出錯誤 (default) :
protect_from_forgery with: :exception
- 相當於關掉檢查 token, 直接通過 :
protect_from_forgery with: :null_session
- 刪舊session, 建新的一條 :
protect_from_forgery with: :reset_session
關掉檢查 authenticity token
測試環境下預設是不檢查的
config/environments/test.rb
config.action_controller.allow_forgery_protection = false
Skip authenticity token
即使你在 ApplicationController 有檢查 authenticity token,但有些需要接外部 POST 值的 API 接口,你可以在那個 controller 加上
skip_before_action :verify_authenticity_token
session & cookie
session
session[:locale] = 'zh-TW'
cookie
cookies[:storage_path] = params[:storage_path]
cookies[:storage_path] = {:value => params[:storage_path], exripes => 1.hour.from_now}
Cache
Write
Rails.cache.write("cache_key1" , 'Hello World!', :expires_in => 5.minutes)
Read
Rails.cache.read("cache_key1")
存入 hash 及取出
source = {
'xx': 'Hello World!'
}
Rails.cache.write("cache_key1" , Marshal.dump(source) , :expires_in => 5.minutes)
source = Marshal.load(Rails.cache.read("cache_key1"))
render && redirect
render
render :new # render new.html.erb
render action: 'new' # 同上
render text: Rails.env # 輸出純文字
render json: @files, status: 200 # same as => status: :ok
render json: user.errors, status: 422 # return Http status code
render json: episode, status: :created, location: episode # :created 等同 201 (新增成功)
redirect
redirect_to root_path
redirect_to post_comment_path(@post, @comment)
redirect_to @user, notice: 'Updated' # 等同 flash[:notice] = 'Updated' 再 redirect
redirect_to(:back) # 回送出時的那一頁
redirect_to request.referer + "#user-#{@user.id}" # back + anchor 是無效的, 必須改成這樣
redirect_to(request.env['HTTP_REFERER']) # 效果同上
redirect_to action: 'profile' # 等同 redirect_to profile_path
redirect_to root_path, :anchor => "user-#{user.id}" # url 加入 anchor : http://xxxxxx.com/#user-33
注意 redner :edit
使用 flash[:notice]
是沒用的(下個 request 才會發生), 要改用 flash.now[:notice] = '...'
輸出 json 立即 stop
return render json: post_params
link: /users/1.json
/users/1.xml
def show
@user = user.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json {render json: @user } # Content-Type: application/json
format.xml {render xml: @user } # Content-Type: application/xml
# Render specific action
format.html { render :action => "edit" }
# JSONP
format.json { render :json => @user.to_json, :callback => "process_user" }
format.json # 預設是 show.js.erb
end
end
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: 'Success!' }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
只要在 url 後面加上 .json
它就可以用 format.json
去做區分,你不需要在 Header 帶 json,因為它不是靠 Content-Type 判斷的,而且它支援 CORS
(Cross-origin resource sharing)
Redirect with flash
flash[:notice] = "Success"
flash[:alert] = "Fail"
redirect_to root_path, notice: "Success"
Actions
before_action
before_action :set_person, except: [ :index, :new, :create ] # except
before_action :ensure_permission, only: [ :edit, :update ] # only
before_action :set_menu, if: :devise_controller? # 只有特定 controller 才讀
其他 actions
- prepend_before_action
- skip_before_action
- append_before_action
- after_action
- prepend_after_action
- skip_after_action
- append_after_action
- around_action
- prepend_around_action
- skip_around_action
- append_around_action
Exception Handling
begin
@cart = Cart.new(cart_params)
@cart.save
@user = User.find(3)
rescue ActiveRecord::RecordNotUnique
logger.info('Unique key 已重覆')
rescue ActiveRecord::RecordNotFound
logger.info('沒有這筆資料')
rescue => e
# 如果以上沒有符合的 error, 都會進這裡
logger.info(e.class) # i.e. ActiveRecord::RecordNotUnique
# retry # 下 retry 要注意,不小心可能會形成無窮迴圈
ensure
"無論是否發生例外都會執行"
end
補捉自訂 exception
def create
@order = check_cart
rescue CartService::CartIsEmpty
flash[:alert] = 'Cart is empty'
rescue ActiveRecord::ActiveRecordError
flash[:alert] = "Something Wrong:#{$!}"
end
class CartIsEmpty < StandardError; end
def check_cart
raise CartIsEmpty, '購物車裡無任何商品' if current_user.carts.empty?
end
擲出其他錯誤
吐 404
raise ActionController::RoutingError.new('Not Found')
$!
(例外物件)
.class
, ex: ZeroDivisionError
.message
, ex: divided by 0
.backtrace
(等同於$@
), 程式出錯的位置, ex: /tmp/test.rb:2:in
concerns - controller 之間共同 method
controllers/concerns 與 models/concerns 是共通的
controllers/concerns/example.rb
module Example
def test
logger.info('TEST TEST TEST')
end
end
controllers/carts_controller.rb
class CartsController < ApplicationController
include CheckCart
def show
test
end
end
includes 避免 n+1 queries 問題
若 posts 有 user_id 欄位, 顯示 post list 時, 每筆 post 後面也要顯示 user name 怎麼撈會比較好 ?
因為要透過 user_id 去關聯 users TABLE 的 name 欄位, 以下是有用 includes 及沒有使用的解析
沒有用 includes :
@posts = Post.all
> Post Load (1.4ms) SELECT "posts".* FROM "posts"
執行了第一次 query
執行迴圈 :
@posts.each do |post|
post.title %> written by <%= post.user.email
end
> User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
> User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
> User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
迴圈每一次執行都會跑一次, 所以執行了 n 次 query
總共是 n+1 次
使用 includes
@posts = Post.includes(:user).all
> Post Load (0.7ms) SELECT "posts".* FROM "posts"
> User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2)
@posts.each do |post|
post.title %> written by <%= post.user.email
end
(不會再產生 query)
只執行了第兩次 query
總共只會執行 2 次 query, 之後迴圈每一次執行都會跟 cache 拿, 所以不會有額外的 query 產生
結論
- 如果要顯示的 list 沒有關聯的問題, 就不需要用 includes
- 如果有的話, 則需要
Logger
如果在 model 或 concerns 下會無法直接取到 logger,需改用 Rails.logger
幾種 log 的方法, 按照越來越嚴重的等級排序 :
- logger.debug : 在 production 下不會紀錄
- logger.info : 一般等級的 log,在 production 也會紀錄
- logger.warn : 警告訊息
- logger.error : 誤訊息,但還不到網站無法執行的地步
- logger.fatal : 嚴重錯誤到網站無法執行的訊息
注意! log 檔案會越來越大,記得要用 logrotate 控制它的檔案大小,可參考此篇
其他
在 controller 使用 NumberHelper 需要另外引入
include ActionView::Helpers::NumberHelper
number_with_delimiter(1000000)
Get controller and action name
controller_name
action_name
在 controller 取得上傳檔名
params[:user][:avatar].original_filename
Database
(最後更新: 2016-05-01)
連接 sqlite3 設定
rails 預設連接的 DB,開發階段才使用
config/database.yml
development:
<<: *default
database: db/development.sqlite3
有個小缺點,即使欄位有限制字數,但 sqlite 仍然可以超出字數且 insert 成功
連接 MySQL 設定
- ubuntu 可能要再安裝
sudo apt-get install libmysqlclient-dev
- 安裝 MySQL 可參考這
-
設定好 config/database.yml
production:
adapter: mysql2
encoding: utf8
database: myapp_production
username: root
password:
host: 127.0.0.1
port: 3306
strict: false # 關閉此模式, 否則存超過 size 的資料會噴 error, 讓它自動截斷
-
Gemfile
gem ‘mysql2’, ‘~> 0.4.3’
- ubuntu 需要再安裝
libmysqlclient-dev
, 否則 bundle 安裝到 mysql2 時會噴錯
- 要強制指定版號, 不然會噴 error :
Specified 'mysql2' for database adapter, but the gem is not loaded. Add
gem ‘mysql2’ to your Gemfile (and ensure its version is at the minimum required by ActiveRecord). (Gem::LoadError)
-
建立資料庫
RAILS_ENV=production rake db:create
-
執行 migrate
rake db:migrate
沒有的話, 建立 table : rails g migration create_videos
註) 連到 MySQL console
rails dbconsole
連接 PostgreSQL 設定
安裝 PostgreSQL 可參考這
-
設定好 config/database.yml
default: &default
adapter: postgresql
pool: 5
timeout: 5000
development:
«: *default
adapter: postgresql
encoding: unicode
database: myapp_development
-
Gemfile
gem ‘pg’
-
建立資料庫 (使用 Postgres 或 rails 的 command 都可以)
createdb myapp_development
或
rake db:create
-
執行 migrate
rake db:migrate
註) 連到 MySQL console
rails dbconsole
欄位
欄位型態
:string
: 有限的字串長度, 如果不指定長度, 預設是 varchar(255)
:text
: 不限的字串長度
:integer
: 整數
:float
: 浮點數
:decimal
: 十進位數
:datetime
: 日期時間
:timestamp
: 時間戳章, 型態為 datetime
:date
: 日期
:time
: 時間
:binary
: 二進位, blob
:boolean
: 布林值
:references
: 用來參照到其他 Table 的外部鍵, 型態為 integer
tinyint 與 boolean
-
MySQL 有 tinyint 欄位, 但 Rails 不支援, 但使用 integer + limit 來取代, 如下
t.integer :status, :limit => 2
-
MySQL 沒有 boolean 的型態, Rails 是用 tinyint(1) 來支援 MySQL 的 boolean
integer 補充
如果沒有指定 limit 的話, 預設會建立 int(11), 但如果需要建立 BIGINT,
又該指定多少的 limit 呢? 參考以下對照表
:limit Numeric Type Column Size Max Value
-----------------------------------------------------------------------
1 TINYINT 1 byte 127
2 SMALLINT 2 bytes 32767
3 MEDIUMINT 3 bytes 8388607
4 INT(11) 4 bytes 2147483647
8 BIGINT 8 bytes 9223372036854775807
預設 INT(11) = limit 4
add_column :users, :money, :integer
BIGINT = limit 5~8
add_column :users, :money, :integer, limit: 8
DB Migration
產生 migration 指令及命名慣例
rails g migration create_users # 建立 TABLE
rails g migration add_confirmable_to_devise # 新增欄位
rails g migration change_comment_field_name # 修改欄位
Command
rake db:create # 建立目前 RAILS_ENV 環境的資料庫
rake db:create:all # 建立所有環境的資料庫
rake db:drop # 刪除目前 RAILS_ENV 環境的資料庫
rake db:drop:all # 刪除所有環境的資料庫
rake db:migrate # 執行 migration
rake db:migrate RAILS_ENV=development # 指定 development 環境執行 migration
rake db:migrate:up VERSION=20150713155732 # 執行特定版本的 migration
rake db:migrate:down VERSION=20150713155732 # 回復特定版本的 migration
rake db:rollback # To rollback the previous migration
rake db:rollback STEP=3 # 回復前 3 個 migration
rake db:version # 顯示 Current version: 20150713155732
rake db:seed # 執行 db/seeds.rb (種子資料)
rake db:schema:dump # Dump the current db state. 產生 db/schema.rb
rake db:setup # Creates the db, loads schema, & seed. ( When you start working on an existing app
- 不加上 RAILS_ENV 預設就是 development
- rails 的 db 可以分為 production 跟 development, 所以可以用 RAILS_ENV 指定哪個 DB
語法
migrate 的方式
def up # migrate 執行的
def down # rollback 執行的
def change # migrate 執行的, 要嘛就 up + down, 不想那麼麻煩就選擇 change
Create Table & 欄位
create_table(:users) do |t|
t.column :id, 'INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (id)'
t.string :id, primary_key: true # create_table :users, id: false
t.string :video_id, :limit => 50, :null => false # primary key 但要自己產生
t.integer :kind , :limit => 1 , :default => 0 , :null => false , :unsigned => true
t.boolean :is_hidden, :default => false ,:null => false
t.datetime "delete_at"
t.string "title" limit: 100, default: 0, null: false (VARCHAR)
t.text "body" (TINYTEXT, TEXT, MEDIUMTEXT, or LONGTEXT2)
t.timestamps (same as : t.datetime :created_at, :updated_at)
t.references :article, index: true
end
操作欄位
- 改default :
change_column_default :users, :is_admin, default: true
- 建立table :
create_table :videos do |t|
- 刪除table :
drop_table :people
- 變更欄位 :
change_column :table_name, :field_name, :integer, :limit => 8, :unsigned => true, :null => false, :auto_increment => true
- 增加欄位 :
add_column :table_name, :balance, :integer, default: 0, null: false, unsigned: true
- 刪除欄位 :
remove_column :table_name, :created_at
- rename欄位 :
rename_column :table_name, :id, :udid
- 增加索引 :
add_index :table_name, :email, unique: true
- 增加組合索引 :
add_index :user_views, [:user_id, :article_id]
- 執行SQL :
execute "ALTER TABLE users modify COLUMN id int(8) AUTO_INCREMENT"
Example
change_column :videos, :source_website, :string, :limit => 50, :null => false
add_column :videos, :file_name, :string, :limit => 100, :null => false
add_index :videos, :file_name, :unique => true
add_index :videos, :source_website
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
add_index "users_roles", ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
欄位參數
default: 'Hello'
limit: 30
null: false
first: true # position
after: :email # position
unsigned: true
當做 migration 時,這個屬性 rails 本身是不支援的,雖然執行 migrate 不會噴錯誤,但是你可以去 db/schema.rb
看,unsigned: true
是沒有被寫進去的
Assets
Command
清除 assets cache
rake assets:clean
precompiled 所有 assets
rake assets:precompile
RAILS_ENV=production bundle exec rake assets:precompile
Custom assets folder
config/application.rb
在 class 裡加上 :
config.assets.paths << Rails.root.join("app", "assets", "[custom foler name]")
加入 fonts
-
config/application.rb
config.assets.paths « Rails.root.join(“app”, “assets”, “fonts”)
??) config/initializers/assets.rb, 不加也能 work, 但先保留
# Rails.application.config.assets.precompile += %w( .svg .eot .woff .ttf )
assets/fonts
path
font-awesome.min.css.erb :
src:url(<%= asset_path 'dashboard/fontawesome-webfont.eot' %>);
它會被 compile 成
src:url(/assets/dashboard/fontawesome-webfont-e511891d3e01b0b27aed51a219ced5119e2c3d0460465af8242e9bff4cb61b77.eot);
如果不用預設的 application.js
application.css
不用預設的 application, 在 controller 帶值給 layout load asset, 就必須要寫在 config/initializers/assets.rb :
Rails.application.config.assets.precompile += %w( welcome.js )
Rails.application.config.assets.precompile += %w( welcome.css )
Rails.application.config.assets.precompile += %w( videofrom.js )
Rails.application.config.assets.precompile += %w( videofrom.css )
或加入所有的 js 及 css
config.assets.precompile += Dir["#{__dir__}/../app/assets/stylesheets/*.css"].map{|i|i.split('/').pop}
config.assets.precompile += Dir["#{__dir__}/../app/assets/javascripts/*.js"].map{|i|i.split('/').pop}
讀取 Javascript / CSS
<%= javascript_include_tag "application" %>
<%= stylesheet_link_tag "application" %>
依 controller name 決定讀取的 JS
view : (讓 controller 讀自己的 javascript)
<%= stylesheet_link_tag controller_name, media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag controller_name, 'data-turbolinks-track' => true %>
require 語法說明
//= require jquery
//= require jquery.turbolinks # 一定要馬上在 jquery 後面 load
//= require jquery_ujs
//= require turbolinks
/app/assets/javascripts/application.js :
//= require jquery
//= require jquery_ujs # unobtrusive JavaScript. Ajax
//= require_tree . # include all files
//= require application # include application.js
//= require shared # include lib/assets/javascripts/shared.js.coffee
//= require friend # include vendor/assets/javascripts/friend.js
/app/assets/stylesheets/application.css :
/*
*= require reset # Included before the content in this file
*= require_self # Specifies where to insert content in this file
*= require_tree .
*/
form.new_user {
border: 1px dashed gray; # include before everything else
}
Helper
Assets helper 使用後都會加上 hash, 讓 browser 判斷是否拿 cache
audio_path("horse.wav") # => /audios/horse.wav
audio_tag("sound") # => <audio src="/audios/sound" />
font_path("font.ttf") # => /fonts/font.ttf
image_path("edit.png") # => "/images/edit.png"
image_tag("icon.png") # => <img src="/images/icon.png" alt="Icon" />
video_path("hd.avi") # => /videos/hd.avi
video_tag("trailer.ogg") # => <video src="/videos/trailer.ogg" />
引入
src: url('/assets/fonts/myfont-webfont.ttf')
src: url('myfont-webfont.eot?#iefix') format('embedded-opentype');
在副檔名加入 .erb, ex: application.css.erb
src: url(<%= asset_path 'Chunkfive-webfont.eot' %>);
或直接用 public 路徑, 不管是 assets/fonts, assets/js, 最後都是同一層目錄結構, 所以可以在 css 下直接引入 /assets/web-icons.woff
位在 /assets/fonts/web-icons.woff
的檔案
Sass helper
將原本 .css
檔 rename 為 .scss
, 並根據以下修改
image-url("rails.png") # => url(/assets/rails.png)
image-path("rails.png") # => "/assets/rails.png".
asset-url("rails.png", image) # => url(/assets/rails.png)
asset-path("rails.png", image) # => "/assets/rails.png"
用法
src: asset-url('Junction-webfont.eot', font);
background-image:image-url('application/typewriter_dark.jpg');
src: url(font-path('myfont-webfont.eot')
src: font-url('icofonts.eot'); # compile css 後 : src: url(/assets/icofonts.eot);
src: url(font-path('myfont-webfont.eot')
src:url(/assets/dashboard/web-icons/web-icons.eot?v=0.2.2) # web-icons.eot 的路徑在 app/assets/fonts/dashboard/web-icons/web-icons.eot
改成
src:asset-url("dashboard/web-icons/web-icons.eot?v=0.2.2")
如何套入 bootstrap dashboard theme
將會引入的 css 放在 assets/stylesheets/dashboard
下
assets/stylesheets/dashboard.css
/*
*= require_tree ./dashboard
*/
將會引入的 js 放在 assets/javascripts/dashboard
下
assets/javascripts/dashboard.js
/*
*= require ./dashboard/jquery.min.js
*= require_tree ./dashboard
*/
將會引入的 fonts 放在 assets/fonts/dashboard
下
需再做以下步驟
-
config/application.rb 加入 assets_path
-
initialize/assets.rb 加入 precompile
-
修改 css 引入的 fonts
- 將 url 改成 asset-url 參考本文
Sass helper -> 用法
- 如果網站使用的是 HTTPS, 那麼如果 css 有引入 http 的 font 都要改成 https, 否則瀏覽器會拒絕載入
Troubleshootings
ActionController::RoutingError (No route matches [GET] “/assets/dashboard/jquery.min.map”)
能將 minify 後的變數從 a b c 轉回原本的, 它在 jquery.min.js 最後一行 //# sourceMappingURL=jquery.min.map
, 如果不需要可以直接刪掉
讓 js 檔可以取得 Route Path
application.js.erb : (注意! 副檔名是 .erb
)
Rails.application.routes.url_helpers.*_path
# ex:
Rails.application.routes.url_helpers.edit_post_path
Custom helper
- View 在不需要 controller include 可以直接用 helper 定義的 method
- controller 要用 定義的 helper 一定要 include
app/helpers/event_helper.rb
module EventsHelper
def do_something
end
end
controller :
class BadgeController < ApplicationController
include EventHelper
....
end
Mailer
SMTP 設定
config/application.rb
config.action_mailer.default_url_options = { host: $settings[:host] }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.mailgun.org",
port: 587,
user_name: "postmaster@example.com",
password: "9******************************d",
}
寄信範例
app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
# 如果有設定的話, 在寄信時就不用特別指定
default from: 'contact@gmail.com'
default to: 'test@gmail.com'
layout 'mailer'
end
app/mailers/my_mailer.rb
class MyMailer < ApplicationMailer
def welcome(user)
@user = user
mail(to: @user.email, subject: 'Welcome')
end
end
寄給多位 User : to: 'aaa@gmail.com,bbb@gmail.com'
app/views/layouts/mailer.text.erb
<%= @user.nickname %> 您好~
<%= yield %>
如果有任何疑問,歡迎隨時聯絡我們
謝謝您選擇我們,期待與您的合作。
app/views/my_mailer/welcome.text.erb
Welcome to example.com, <%= @user.name %>
app/controllers/user_controller.rb
MyMailer.welcome(current_user).deliver_later
寫完一封 mail 可以在 console 下執行, 測試信件是否發送出去
Send to multiple recipients
emails = @recipients.collect(&:email).join(",")
mail(to: emails, subject: "A replacement clerk has been requested")
cc & bcc
cc 看到的所有副本的收件人 email, bcc 則看不到
mail(to: recipient.email_address_with_name, bcc: ["bcc@example.com", "Order Watcher <watcher@example.com>"])
reply-to
當收件人按下回覆,預設回覆是給寄件人,但寄件人的 email 往往是客服的信箱,所以可以透過指定 reply-to 去指定收件人
mail(to: user_email, reply_to: [email_1, email_2])
Config
為每條 log 前加上 user_id
Started GET "/" for 127.0.0.1 at 2016-03-31 14:12:52 +0800
加上後 :
[user_id:2] Started GET "/" for 127.0.0.1 at 2016-03-31 14:12:52 +0800
如何加上 :
config/application.rb
config.middleware.delete(ActionDispatch::Cookies)
config.middleware.delete(ActionDispatch::Session::CookieStore)
config.middleware.insert_before(Rails::Rack::Logger, ActionDispatch::Session::CookieStore)
config.middleware.insert_before(ActionDispatch::Session::CookieStore, ActionDispatch::Cookies)
config/initializers/logging.rb
Rails.configuration.log_tags = [
proc do |req|
if req.session["warden.user.user.key"].nil? # 這個 key 是 devise 的
"Anonym"
else
"user_id:#{req.session["warden.user.user.key"][0][0]}"
end
end
]
ref : http://stackoverflow.com/questions/10811393/how-to-log-user-name-in-rails
其他
Difference between .length
, .count
and .size
這三個共同點都是算一個集合的數量,
如果對像是 Array 這兩者的行為無差別
有差別的是有時候我們會利用這兩者去判斷從 DB 撈出來的筆數如果 >0
再做相關的處理,
從 DB 撈出來的是 ActiveRecord::Relation
a = User.all
User Load (115.4ms) SELECT "users".* FROM "users"
使用 .length
, .size
會直接算出數量
a.length
=> 203
a.size
=> 203
使用 .count
就會使用 SQL count, 成本相對是比較高的
a.count
(2.5ms) SELECT COUNT(*) FROM "users"<F6>
=> 203
結論 : 如果只是需要單純判斷目前 DB 資料的筆數用 .count
, 如果不是或不確定用哪個就用 .length
或 .size
nil vs empty vs balnk
首先 blank 是 rails 才有的, ruby 本身是沒有的
nil 可以用在任何物件上, 即使物件為 nil, 當 Object 為 nil 的話為 true
empty 可以用在 strings, arrays and hashes, True 的話有以下三種情況
- String length == 0
- Array length == 0
- Hash length == 0
如果在某個為 nil 的物件上問 .empty?
會擲出 NoMethodError
.blank?
可以解決這個問題, 它不會引發 NoMethodError
, 用法跟 empty 幾乎一樣
nil.blank? = true # empty? 會引發錯誤
false.blank? = true # empty? 會引發錯誤
[].blank? = true
[].empty? = true
{}.blank? = true
{}.empty? = true
"".blank? = true
"".empty? = true
5.blank? = false # empty? 會引發錯誤
0.blank? = false # empty? 會引發錯誤
有一個 space 的情況
" ".blank? = true
" ".empty? = false
Array 是空的情況
[ nil, '' ].blank? == false
[ nil, '' ].all? &:blank? == true
區分環境變數
config/settings.yml
:development:
:host: '127.0.0.1'
:production:
:host: 'example.com'
config/application.rb
# 載入 settings.yml
require 'yaml'
$settings = YAML.load(File.open("#{__dir__}/settings.yml"))[Rails.env.to_sym]
要記得先重啟 rails server 才能讀取新的 config,在程式裡使用,會依照你的環境讀取相對的設定
$settings[:host]
只顯示 date 就好
如果網站上常使用 date 顯示, 一般輸出 created_at
都要用 created_at.strftime('%F %T')
很不方便
可以建一個 config/initializers/time_format.rb
或直接寫在 application.rb
Time::DATE_FORMATS[:default] = "%Y-%m-%d %H:%M:%S"
將物件儲存成字串
Marshal
a = {qq: 'xxx', ff: {cc: 'ccc', dd: 'ddddd'}}
# 轉成字串
Marshal.dump(a)
=> "\x04\b{\a:\aqqI\"\bxxx\x06:\x06ET:\aff{\a:\accI\"\bccc\x06;\x06T:\addI\"\nddddd\x06;\x06T"
# 轉回成物件
Marshal.load("\x04\b{\a:\aqqI\"\bxxx\x06:\x06ET:\aff{\a:\accI\"\bccc\x06;\x06T:\addI\"\nddddd\x06;\x06T")
=> {:qq=>"xxx", :ff=>{:cc=>"ccc", :dd=>"ddddd"}}
JSON
# 轉成 JSON
a.to_json
=> "{\"qq\":\"xxx\",\"ff\":{\"cc\":\"ccc\",\"dd\":\"ddddd\"}}"
# 轉成 json ( hash key)
JSON.parse("{\"qq\":\"xxx\",\"ff\":{\"cc\":\"ccc\",\"dd\":\"ddddd\"}}")
=> {"qq"=>"xxx", "ff"=>{"cc"=>"ccc", "dd"=>"ddddd"}}
# 轉成 <F10>
JSON.parse("{\"qq\":\"xxx\",\"ff\":{\"cc\":\"ccc\",\"dd\":\"ddddd\"}}", symbolize_names: true)
=> {:qq=>"xxx", :ff=>{:cc=>"ccc", :dd=>"ddddd"}}
Marshal 會佔比較多的字符 55 : 43
解開 session
Marshal.load(Base64.decode64(cookie_token.split("--")[0]))
devise 登入使用記住我,cookie_token 才能解
簡化 console 指令
~/.irbrc
class T
def self.mail(mail)
User.where(email: mail)
end
end
Rails console :
T.mail('me@gmail.com)
console 下自動 include (好像沒用了 updated at 2015/9/3)
config/application.rb
config.console do
include Rails.application.routes.url_helpers
end
找出 Gem 的真實路徑
In console
> Gem.loaded_specs['rails'].full_gem_path
=> "/usr/local/rvm/gems/ruby-2.2.1/gems/rails-4.2.3"
Rails lib 真實路徑
ex: upload
/usr/local/rvm/gems/ruby-2.2.1/gems/actionpack-4.2.3/lib/action_dispatch/http/upload.rb