花了兩個(gè)月做了一個(gè)類似 Tower 的GTD工具, 從最開始學(xué)習(xí) Ruby 和 Rails, 到跌跌撞撞中摸索 Rails 的各種小技巧, 直到最后一塊拼圖的完成, 才感覺自己掌握了 Rails 構(gòu)建Web產(chǎn)品方法和技巧.
在自學(xué) Rails 的這幾個(gè)月, 也看了很多現(xiàn)成的文章, 雖然很多文章都寫的非常詳細(xì), 但是對(duì)于初學(xué)者來說, 沒有重點(diǎn)的詳細(xì)只會(huì)給人一種盲人摸象的感覺, 感覺很強(qiáng)大, 心里卻沒有底.
所以, 我一直在想寫一篇重新梳理過的 Rails 教程, 能夠幫助后來的 Rails 自學(xué)者更快的掌握整個(gè) Rails 的脈絡(luò)得以快速上手, 也就是今天的 << Rails入門最佳實(shí)踐 >>
預(yù)備知識(shí)
因?yàn)橛?Rails 開發(fā)Web項(xiàng)目, 首先需要懂 Ruby 編程和基本的 Rails 操作, 所以建議你先把下面這兩本書看完以后再來反過來閱讀本文, 會(huì)有茅塞頓開的感覺, 哈哈哈哈.
- Ruby: Ruby Essentials
- Rails: Ruby on Rails 指南
極簡(jiǎn)理念
就像我剛開始看了上面的兩本書以后, 我依然沒法脫離 "Ruby on Rails指南" 本書來獨(dú)立構(gòu)建項(xiàng)目, 因?yàn)?Web 的知識(shí)太碎了, 你只有把每個(gè)模塊都弄懂并串聯(lián)起來, 你才能知道怎么做, 而看了指南這本書以后, 雖然對(duì)怎么構(gòu)建 Web 項(xiàng)目有一個(gè)簡(jiǎn)單的認(rèn)識(shí), 但是怎么自己構(gòu)建和擴(kuò)展功能, 還是一頭霧水.
所以, 本文是在你看了上面兩本書以后, 針對(duì)這兩本書遺漏和馬上有疑問的點(diǎn), 用極簡(jiǎn)通俗的語言來補(bǔ)齊知識(shí)體系缺失的一環(huán), 希望用最少篇幅讓你知道怎么把所學(xué)的知識(shí)串聯(lián)起來.
極簡(jiǎn)理念分為心法和劍法兩部分, 心法就是告訴你理論應(yīng)該怎么做(讓你先有眼高手低的感覺, 哈哈哈), 劍法就是實(shí)地一步一步的實(shí)踐來疏通你的任督二脈.
心法
學(xué)習(xí) Rails 最重要的是, 需要清楚的知道消息響應(yīng)的循環(huán), 只有知道消息從哪里發(fā)出, 哪里經(jīng)過, 最后以怎樣的形式返回, 才能在問題發(fā)生時(shí)知道怎么準(zhǔn)確的定位問題.
注意看下面這張圖:
- 首先你的瀏覽器訪問網(wǎng)頁的時(shí)候, 比如本地的 0.0.0.0, 瀏覽器會(huì)向 Rails 后臺(tái)發(fā)送 0.0.0.0 的 GET 請(qǐng)求
- 路由器 (Router) 會(huì)直接返回路由中 root 路徑對(duì)應(yīng)的頁面, 這就是我們?cè)L問網(wǎng)站的首頁
- 在首頁中點(diǎn)擊 <a> 標(biāo)簽的連接, 瀏覽器從 <a> 標(biāo)簽中提取 href 路徑繼續(xù)向 Rails 發(fā)送 HTTP 請(qǐng)求
- 路由器根據(jù) href 請(qǐng)求的路徑和路由表, 找到相同名字的控制器(Controller), 并調(diào)用控制器中對(duì)應(yīng)的動(dòng)作 (index, show, edit, update, destroy) 函數(shù)去執(zhí)行
- 控制器動(dòng)作函數(shù)一般會(huì)查詢模型(Model)的數(shù)據(jù), 根據(jù)動(dòng)作函數(shù)中的邏輯來產(chǎn)生不同的請(qǐng)求響應(yīng) (JSON, JS 或者 html.erb 模板文件)
- 瀏覽器根據(jù)控制器返回的請(qǐng)求響應(yīng)來更改當(dāng)前的頁面, 以完成一次完整的消息循環(huán)處理
Rails 在控制器 respond_to 處理響應(yīng)的時(shí)候有四種方式:
- 第一種是直接返回 html.erb 文件, html.erb 文件會(huì)根據(jù)填充數(shù)據(jù)生成新的 html 文件替換現(xiàn)有的頁面
- 返回 js.erb 文件, 根據(jù)填充數(shù)據(jù)生成新的 js 文件直接在當(dāng)前頁面中執(zhí)行代碼操作 DOM 元素
- 返回 json 數(shù)據(jù)給 ajax , 由瀏覽器中 ajax的js回調(diào)來處理返回的 json 數(shù)據(jù), 并操作當(dāng)前頁面的 DOM 元素
- 當(dāng)然也可以直接用
redirect_to new_path
跳轉(zhuǎn)到 new_path 頁面
只要把這個(gè)消息響應(yīng)循環(huán)銘記于心, 你已經(jīng)就理解了 Rails 一半了, 雖然 Rails 功能強(qiáng)大, 但是本質(zhì)上 Rails 就是按照上面的消息循環(huán)來處理所有的 HTTP 請(qǐng)求.
劍法
劍法部分主要是實(shí)操, 主要分為四個(gè)部分:
- 三板斧, 三板斧學(xué)會(huì)了Web項(xiàng)目馬上就可以上手
- 錦囊妙計(jì), 提供各種小技巧來靈活解決一些現(xiàn)實(shí)中的障礙
- 目錄參考, 講解一下Web開發(fā)最主要需要了解的一些目錄
- 擴(kuò)展閱讀, 通過系統(tǒng)學(xué)習(xí)某一本書來夯實(shí)知識(shí)體系
第一板斧: 從 Restful 的角度打通路由丘喻、控制器、視圖
看了很多關(guān)于 Rails 的文章, 上來就說, 你要做這個(gè)命令, 那個(gè)命令, 然后你就可以訪問到頁面了.
其實(shí)我們?cè)倩叵胍幌?"消息響應(yīng)循環(huán)" 那張圖, Web本質(zhì)是什么?
Web的本質(zhì)就是用戶點(diǎn)擊HTML的連接, 更新當(dāng)前頁面.
更新的方式可以是直接跳轉(zhuǎn), 或者用新的頁面替換, 或者JS直接操作DOM元素
在本質(zhì)之上, 才是各種工具, 插件,性能優(yōu)化和架構(gòu)設(shè)計(jì)等.
然而 Rails 的本質(zhì)是什么呢?
Rails 的本質(zhì)就是根據(jù) Restful 的設(shè)計(jì)原則來修改路由, 控制器和模板等文件
使得 Rails 能夠按照 "消息響應(yīng)循環(huán)" 一直循環(huán)下去
所以, Rails 的大部分工作, 就是下面這幾件事情:
- 改路由 (routes.rb), 讓Rails可以根據(jù)發(fā)送過來的 href 連接和路由設(shè)置去找到控制器和對(duì)應(yīng)的動(dòng)作函數(shù)
- 創(chuàng)建對(duì)應(yīng)的控制器 (resources_controller.rb) 并添加動(dòng)作函數(shù) (action)
- 在動(dòng)作函數(shù)中, 從模型 (resources.rb) 中抓取數(shù)據(jù), 根據(jù) respond_to 表達(dá)式來返回響應(yīng)結(jié)果 (html.erb, js.erb, json, redirect_to)
- 修改 js 代碼來處理 DOM 元素
Rails 的所有開發(fā)都是圍繞著上面這四點(diǎn)來展開的.
說到啥是 Restful API ? 不用想什么高大上的概念, 其實(shí)就是結(jié)合訪問資源的路徑 + HTTP的請(qǐng)求類型來自動(dòng)對(duì)應(yīng) controller.rb 里面不同動(dòng)作函數(shù) (index, new, edit, show, update, destroy) 的規(guī)范和技術(shù).
不同的動(dòng)作函數(shù)在所有資源中對(duì)應(yīng)的意義是一致的:
- index 就是顯示資源的列表
- show 就是顯示某一個(gè)資源
- new 就是創(chuàng)建資源
- edit 就是編輯資源
- update 就是更新資源
- destroy 就是刪除資源
我們以任務(wù)的圖來形象的給出不同動(dòng)作最終應(yīng)該生成的頁面長(zhǎng)什么樣子?
所以, 如果我們要建立一批像上面 "任務(wù)" 的頁面, 我們應(yīng)該怎么做?
- 修改 config/routes.rb 增加 missions 這個(gè)資源:
Rails.application.routes.draw do
resources :missions
end
- 創(chuàng)建 app/controllers/missions_controller.rb 這個(gè)文件, 并創(chuàng)建動(dòng)作函數(shù):
class MissionsController < ApplicationController
def index
end
def show
end
def new
end
def edit
end
def update
end
def destroy
end
end
- 如果默認(rèn)只是返回動(dòng)作對(duì)應(yīng)的模板頁面, 上面的函數(shù)不用加什么內(nèi)容, 直接在 app/views/missions 目錄中分別創(chuàng)建 index.html.erb , show.html.erb, new.html.erb, edit.html.erb, update.html.erb, destroy.html.erb 的模板文件即可.
上面就是Rails創(chuàng)建頁面的核心步驟, 只要你完成上面的步驟后, 在頁面中加入 <a> 標(biāo)簽連接后, Rails 會(huì)按照下面的對(duì)照方式自動(dòng)渲染頁面的:
Rails Path | Url Path | Http Type | Controller action |
---|---|---|---|
missions_path | /missions | GET | index |
missions_path | /missions | POST | create |
new_team_mission_path | /missions/new | POST | new |
edit_mission_path | /missions/id/edit | GET | edit |
missions_path | /missions/id | GET | show |
missions_path | /missions/id | PATCH | update |
missions_path | /missions/id | DELETE | destroy |
請(qǐng)仔細(xì)看上面的圖, 在 Rails 中, 不同的 PATH 和 不同的 HTTP 請(qǐng)求類型對(duì)應(yīng)就會(huì)自動(dòng)讓 Rails 去控制器中找對(duì)應(yīng)的 action, 然后根據(jù) action 的名字生成對(duì)應(yīng)的 html 模板文件.
所以, 最后總結(jié)來說, 在 Rails 里你需要添加一個(gè)新的資源來處理頁面, 假設(shè)這個(gè)資源叫 foo, 你需要做的就是再重復(fù)上面的步驟:
- rails g model foo 生成一個(gè)數(shù)據(jù)庫表
- routes.rb 加上
resources foo
- 創(chuàng)建 foos_controller.rb 文件
- 在目錄下 view/foos/ 創(chuàng)建動(dòng)作對(duì)應(yīng)的 *.html.erb 文件
第二版斧: 獨(dú)善其身的模型
當(dāng)你執(zhí)行命令 rails g model foo attr_a:integer attr_b:integer
生成模型的時(shí)候, 也就創(chuàng)建了
app/models/foo.rb 文件:
class Foo < ApplicationRecord
end
在模型文件中, 我們是可以直接訪問數(shù)據(jù)表的屬性的, 比如下面這樣:
class Foo < ApplicationRecord
def print_attr
printf("%<attr_a>s %<attr_b>s", attr_a: attr_a, attr_b: attr_b)
end
end
第二扳斧比較簡(jiǎn)單, 就是模型文件中只放一些跟數(shù)據(jù)表屬性相關(guān)的工具函數(shù)就可以了, 跟產(chǎn)品業(yè)務(wù)相關(guān)的代碼全部都放到控制器 foos_controller.rb 中.
上面的 print_attr 是 Foo 的實(shí)例函數(shù), 如果需要?jiǎng)?chuàng)建 Foo 的類函數(shù)需要使用下面的方式:
class Foo < ApplicationRecord
class << self
def class_func
printf("I'm a class function")
end
end
end
然后就可以直接使用 Foo.class_func
來調(diào)用了.
還有一些非常獨(dú)特的情況, 就是針對(duì)模型函數(shù)會(huì)在多個(gè)控制器中使用, 甚至是多個(gè) *.html.erb 模板文件中調(diào)用, 這時(shí)候就可以用 helper 模塊來處理.
比如我們創(chuàng)建一個(gè)跟 session 相關(guān)的 helper 模塊 app/helpers/sessions_helper.rb
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
end
然后在 app/controllers/application_controller.rb 文件中加入 include 語句:
class ApplicationController < ActionController::Base
include SessionsHelper
end
這樣, 我們就可以在所有的模式、控制器祥款、視圖文件中訪問 log_in
函數(shù)了.
第三板斧
前兩板斧主要講了怎么生成資源頁面和模型文件中功能函數(shù)的處理, 現(xiàn)實(shí)的Web場(chǎng)景不僅僅是跳轉(zhuǎn)到新的頁面或者大范圍更新當(dāng)前頁面的內(nèi)容, 更多的場(chǎng)景是, 根據(jù)用戶的交互行為, 局部微小的改變一小塊界面來進(jìn)行視覺反饋.
第三板斧講的主要就是怎么在 Rails 中使用 JavaScript, JavaScript的代碼編寫的時(shí)候, 時(shí)刻問自己, 從哪里發(fā)起 submit 或者 ajax ? 發(fā)送完成后是否要處理返回?cái)?shù)據(jù)?
JavaScript 主要有四種情況:
1. 表單的類型, 只提交, 不處理返回?cái)?shù)據(jù)
<%= form_for(:session, url: sign_in_path) do |f| %>
<%= f.email_field :email, class: "form-control", placeholder: "登錄郵箱" %>
<%= f.password_field :password, class: "form-control", placeholder: "密碼" %>
<%= f.submit "登錄", class: "btn btn-primary" %>
<% end%>
比如這種最簡(jiǎn)單的, 在 form_for
里面直接點(diǎn)擊按鈕, 就執(zhí)行 submit 動(dòng)作, 這種情況, 我們只需要控制 form_for 里的 url 字段, 也就是前面說的資源路徑, form_for 會(huì)自動(dòng)用 POST 請(qǐng)求調(diào)用資源的 create 動(dòng)作函數(shù)的.
2. ajax 發(fā)送異步請(qǐng)求, 并處理返回的模板數(shù)據(jù):
$.ajax({type: "POST",
url: "/missions/1",
success: function(result) {
}
})
def edit
respond_to do |format|
format.html do
render "_foo",
layout: false
end
end
end
上面這段代碼就是最簡(jiǎn)單的模板返回樣例, format.html 的意思就是返回一個(gè)模板, render 的意思就是直接渲染模板 app/views/layouts/_foo.html.erb 的內(nèi)容
layout: false
這句強(qiáng)調(diào)的是不要在 _foo.html.erb 外包裹其他模板, 文件 _foo.html.erb 顯示的是啥內(nèi)容就用啥內(nèi)容, 因?yàn)楹芏鄷r(shí)候 _foo.html.erb 模板在作為 ajax 的返回的數(shù)據(jù)并代表一個(gè)完整的頁面, 而僅僅只是一個(gè) div 的模板內(nèi)容用于替換局部的DOM元素.
3. ajax 發(fā)送異步請(qǐng)求, 并處理返回的 json 數(shù)據(jù)
$.ajax({type: "POST",
url: "/missions/1",
success: function(result) {
console.log(result["status"])
}
})
def edit
respond_to do |format|
format.json do
render json: { status: "destroy" }
end
end
end
和返回模板數(shù)據(jù)的格式類似, 唯一的不同是 format.html 變成了 format.json, 然后通過 json: { key: "value" }
的形式返回JSON數(shù)據(jù)給 ajax 請(qǐng)求.
4. 第三方插件發(fā)送表單請(qǐng)求, 在 ajax 成功后執(zhí)行JS代碼
我們來舉一個(gè)簡(jiǎn)單的例子, 如果我們要實(shí)現(xiàn)上圖中這種上傳頭像后自動(dòng)更新頁面中兩處頭像元素的功能, 我們一般會(huì)按照下圖這種流程來處理:
- 首先我們會(huì)在 form_with 表單中增加 data-controller 和 data-action 字段, 表示 AJAX 成功返回結(jié)果后, 調(diào)用 app/javascript/controllers/user_controller.js的 update 函數(shù), submit 按鈕點(diǎn)擊后提交數(shù)據(jù)給 user_controller.rb 中的 update 函數(shù)
- user_controller.rb 控制器在處理數(shù)據(jù)后, 返回的并不是 JS 文件, 而是返回 JSON 數(shù)據(jù)
- Stimulus 的JS文件在接到 AJAX 返回的 JSON 數(shù)據(jù)后在瀏覽器端修改 HTML頁面的DOM結(jié)構(gòu)
下面就是抽象的消息響應(yīng)循環(huán):
因?yàn)橄襁@種第三方插件, 有時(shí)候我們往往并不能用 ajax 簡(jiǎn)單的替換 submit 的操作, 所以這種情況, 我們依然會(huì)采用 form_with 的方式提交表單, 但是會(huì)在 ajax:success 的方式使得請(qǐng)求返回 JSON 數(shù)據(jù)的時(shí)候調(diào)用我們制定的 JS 函數(shù).
這其中的關(guān)鍵就是
data-action: "ajax:success->user#update"
這一句, 這一句起到了承上啟下的作用, 這種方法相對(duì)于 Rails 傳統(tǒng)的 SJR 方法的優(yōu)勢(shì)可以參考我的另一篇文章 SJR 結(jié)合 Stimulus 構(gòu)建可維護(hù)的JavaScript代碼
第四種情況比較復(fù)雜, 沒有前面三種那么簡(jiǎn)單易懂, 還需要對(duì) StimulusJS 和傳統(tǒng)的 SJR 方式比較了解, 建議看最后推薦的 StimulusJS Handbook 進(jìn)一步學(xué)習(xí).
錦囊秘籍
到目前為止, 如果你掌握了我上面說的三板斧的內(nèi)容, 其實(shí)你已經(jīng)可以流暢的使用 Rails 來開發(fā) Web 項(xiàng)目了.
下面將會(huì)針對(duì)一些 Rails 的小技巧逐一分享, 以幫助才學(xué)習(xí)完 "Rails on Ruby指南" 后的很多困惑
嵌套路由和HashID
我們經(jīng)常會(huì)看到很多Web項(xiàng)目的連接都長(zhǎng)這樣:
http://0.0.0.0:3000/projects/9KRL0W296QZXO4RP1HD37JGDPNVE1MY8/missions/KVM8OGX0NWK69L9ERHWLD2P4RYE1J3Q5
這種嵌套路由的好處就是, 可以讓開發(fā)團(tuán)隊(duì)有一個(gè)明顯的從屬關(guān)系, 讓產(chǎn)品更有邏輯性, 其實(shí)在 Rails 中做嵌套路由是非常簡(jiǎn)單的, 只用在 routes.rb 中寫下下面的內(nèi)容即可:
Rails.application.routes.draw do
resources :projects do
resources :missions
end
end
Rails會(huì)在你訪問 mission 資源的時(shí)候, 自動(dòng)在 mission 資源前面加入 project 的前綴.
你會(huì)發(fā)現(xiàn), 上面的 projects 和 missions 后面的 ID 感覺都是 hash 過的, 這種處理一般是防止通過資源的數(shù)字id猜出產(chǎn)品的規(guī)模.
HashID 的處理很簡(jiǎn)單, 只用在 Gemfile 加入
gem "hashid-rails"
以后, 在需要處理的資源文件中加入 include Hashid:Rails 的語句
class Mission < ApplicationRecord
include Hashid::Rails
end
這樣訪問路徑的時(shí)候就自動(dòng) Hash 資源的數(shù)字 id
通過 HashID 可以反向獲取對(duì)象, 就像這樣:
mission = Mission.find_by_hashid("hashid_string")
子模板的嵌入和參數(shù)判斷
為了避免重復(fù)代碼, 我們有時(shí)候會(huì)在模板中再嵌入之模板, 一般都使用下面類似的代碼:
<%= render "layouts/foo" %>
如果子模板 app/views/layouts/_foo.html.erb 中可以傳入 keyword 的參數(shù), 但是上面的代碼調(diào)用子模板時(shí)卻什么參數(shù)都沒有調(diào)用, 可以在子模板中使用 local_assigns.has_key? 的方式來判斷參數(shù)是否傳入:
<% if local_assigns.has_key? :keyword %>
...
<% else %>
...
<% end %> >
手機(jī)頁面的處理
因?yàn)?Turbolinks 的加速處理, Rails 渲染手機(jī)頁面也是非常快速的, 但是怎么針對(duì)手機(jī)的尺寸單獨(dú)進(jìn)行手機(jī)頁面適配呢?
首先需要在 Gemfile 加入瀏覽器設(shè)備探測(cè)的插件:
gem "browser"
再在 app/controllers/application_controller.rb 文件中加入:
class ApplicationController < ActionController::Base
before_action :detect_browse_device
private
def detect_browse_device
request.variant = :phone if browser.device.mobile?
end
end
最后在 views/foos/ 目錄下, 新建一個(gè) new.html+phone.erb 的模板就行了, Rails 如果發(fā)現(xiàn)手機(jī)訪問時(shí)會(huì)優(yōu)先調(diào)用 *html+phone.erb" 的模板文件.
我們只需要很少的幾個(gè) rails 腳手架命令
如果你看完 "Ruby on Rails 指南" 就會(huì)發(fā)現(xiàn), 按照我上面的文章, 新建控制器涵叮、視圖等文件都不需要 rails 的腳手架命令, 手動(dòng)創(chuàng)建文件的方式更簡(jiǎn)單也讓我們更加深刻的理解了 Rails 的消息響應(yīng)流程, 所以我強(qiáng)烈建議除了創(chuàng)建數(shù)據(jù)表的時(shí)候我們使用 rails 腳手架命令, 其他的都全部用手工創(chuàng)建文件的方式來執(zhí)行.
我平常需要使用 rails 腳手架的命令只有下面這幾個(gè):
- rails s: 啟動(dòng)服務(wù)器
- rails c: 啟動(dòng)調(diào)試終端
- rails g model Activity mission_id:integer content:string : 創(chuàng)建一個(gè)兩列的 Activity 模型
- rails g migration add_user_id_to_activity user_id:integer : 給現(xiàn)有的模型增加一列
- rails g migration rename_activity_type_column : 重命名一列
- rails g migration RemoveAvatarThumbFromUsers avatar_thumb:string : 從現(xiàn)有模型刪除一列
- rake db:migrate : 合并數(shù)據(jù)表變動(dòng)
- rake db:purge : 清空數(shù)據(jù)庫內(nèi)容
常用的Rails目錄簡(jiǎn)介
Rails 是一個(gè)約定即為規(guī)則的框架, 文件放錯(cuò)了就沒效果了, 下面是一些常用目錄的位置及作用說明
文件路徑 | 說明 |
---|---|
Gemfile | 包管理文件 |
config/routes.rb | 路由配置文件 |
app/controllers | 控制器目錄 |
app/models | 模型目錄 |
app/views | 視圖目錄 |
app/helpers | helper模塊目錄 |
app/javascript/controllers | StimulusJS控制器目錄 |
app/assets/stylesheets/custom | 自定義CSS文件 |
db/migrate | 數(shù)據(jù)庫合并記錄目錄 |
擴(kuò)展閱讀
上面的內(nèi)容主要是針對(duì)初學(xué) Rails 并期望快速實(shí)戰(zhàn)上手的同學(xué), 但是開發(fā)一個(gè)完整的Web產(chǎn)品還需要學(xué)習(xí) CSS铅辞、JQuery以及Rails才推出的 StimulusJS, 這些都需要系統(tǒng)性的學(xué)習(xí)才能融匯貫通.
- CSS揭秘 CSS高級(jí)編程技巧書籍, 特別是偽元素和各種奇淫技巧, 可以非常優(yōu)雅的實(shí)現(xiàn)復(fù)雜的界面效果
- 鋒利的 JQuery 系統(tǒng)的學(xué)習(xí)JQuery 的好書
- StimulusJS Handbook StimulusJS 能夠非常清晰的、模塊化的開發(fā)JS功能, 同時(shí)保持JS代碼的可維護(hù)性
- BootStrap 對(duì)多平臺(tái)自適應(yīng)做的很好, 內(nèi)置很多方便的小控件, 不用自己造. ;)
上面是一些擴(kuò)展閱讀和連接, 希望今天的文章能夠幫助更多人的人更快入門 Rails 全棧開發(fā).