這種表示。
13 |
14 | ### 本章練習主題
15 |
16 | * 學會寫出 CRUD 七個 action 的 controller 與 view
17 | * 學會利用 migration 新增資料庫欄位
18 | * 學會撰寫「表單」
19 | * 學會設定 Route
20 | * 學會 resources 的設定 (單層 resources)
21 | * 對 Rails RESTful 有初步的理解
22 | * 知道 before_action 使用的場景,並如何應用
23 |
24 |
25 |
26 | {::pagebreak :/}
27 |
28 | ## Ch 1.0 CRUD
29 |
30 | CRUD 指的是 Create(新增)、Read(讀取)、Update(更新)、Destroy(刪除)四種操作資料的基本方式,這也是開發網站時幾乎大家最常寫到的四種功能。
31 |
32 | Rails 在開發上極具優勢的其中一個原因,就是使用了 RESTful 的機制以及 內建的 Convention,在實作 CRUD-like 功能時,隔外顯現開發速度上的優勢。
33 |
34 | ### 實作課題
35 |
36 | 本章將會實作以下課題
37 |
38 | * 產生 Group 與 Post 這兩個 Model
39 | * Group 需要有名稱 title, description
40 | * Post 需要有文章標題 文章內容 content
41 | * 寫出 Group 的 CRUD controller 與 View
42 | * 寫出 Posts 的 CRUD controller 與 View
43 | * 在 routing 中對 Group 和 Posts 分別宣告它們都是 resources
44 |
45 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-0-extra-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## 補充章節:RESTful on Rails
4 |
5 | Representational State Transfer,簡稱 REST。是 Roy Fielding 博士在2000年他的博士論文中提出來的一種軟體架構風格。目前是一種相當風行的 Web Services 實現手法,因為 REST 風格的 Web Services 遠比傳統的 SOAP 與 XML-RPC 來的簡潔。在近年,幾乎所有各大主流網站的 API 都已採用此種風格進行設計。
6 |
7 | ### What is REST?
8 |
9 | REST 提出了一些設計概念和準則:
10 |
11 | 1. 網路上的所有事物都被將被抽象成資源 (resource)
12 | 2. 每個資源對應一個唯一的 resource identifier
13 | 3. 通過通用的介面 (generic connector interface) 對資源進行操作
14 | 4. 對資源的各種操作不會改變 resource idetifier
15 | 5. 所有的操作都是無態 (stateless) 的
16 |
17 | 對照到 web services 上來說:
18 |
19 | * resource indentifier 是 URI
20 | * generic connector interface 是 HTTP
21 |
22 | {::pagebreak :/}
23 |
24 | ### RESTful Web Services
25 |
26 | RESTful Web Services 是使用 HTTP 並遵循 REST 設計原則的 Web Services。它從以下三個方面資源進行定義:
27 |
28 | * URI,比如:http://example.com/resources/。
29 | * Web Services accept 與 return 的 media type,如:JSON,XML ,YAML 等。
30 | * Web Services 在該 resources 所支援的一系列 request methid : 如:POST,GET,PUT 或 DELETE。
31 |
32 | 以下列出在實現 RESTful Web 服務時HTTP請求方法的典型用途。
33 |
34 | #### GET
35 |
36 | |資源 | GET |
37 | |-------------------|-----------|
38 | |一組資源的URI,比如http://example.com/resources/ | 使用給定的一組資源替換當前整組資源。|
39 | |單個資源的URI,比如http://example.com/resources/142 | 獲取 指定的資源的詳細信息,格式可以自選一個合適的 media type(如:XML、JSON等)|
40 |
41 | #### PUT
42 |
43 | |資源 | PUT|
44 | |-------------------|-----------|
45 | |一組資源的URI,比如http://example.com/resources/ | 列出 URI,以及該資源組中每個資源的詳細信息(後者可選)。|
46 | |單個資源的URI,比如http://example.com/resources/142 | 替換/創建 指定的資源。並將其追加到相應的資源組中。|
47 |
48 | #### POST
49 |
50 | |資源 | POST|
51 | |-------------------|-----------|
52 | |一組資源的URI,比如http://example.com/resources/ | 在本組資源中創建/追加一個新的資源。 該操作往往返回新資源的URL。|
53 | |單個資源的URI,比如http://example.com/resources/142 | 把指定的資源當做一個資源組,並在其下創建/追加一個新的元素,使其隸屬於當前資源。|
54 |
55 | {::pagebreak :/}
56 |
57 | #### DELETE
58 |
59 | |資源 | DELETE|
60 | |-------------------|-----------|
61 | |一組資源的URI,比如http://example.com/resources/ | 刪除 整組資源。|
62 | |單個資源的URI,比如http://example.com/resources/142 | 刪除 指定的元素。|
63 |
64 | {::pagebreak :/}
65 |
66 | ### 制約即解放
67 |
68 | DHH 在 [Discovering a world of Resources on Rails](http://www.slideshare.net/vishnu/discovering-a-world-of-resources-on-rails) 提到一個核心概念:Constraints are liberating 。
69 |
70 | 很多剛踏入 Rails 這個生態圈的開發者,對於 REST 總有股強烈的反抗心態,認為 REST 是一個討厭的限制。但事實上,DHH 卻認為引入 REST 的制約卻反為 Rais 開發帶來了更大的解放,而這也是他引入這個設計的初衷。
71 |
72 | #### 維護性:解決了程式碼上的風格不一
73 |
74 | 在傳統的開發方式中,對於有效組織網頁應用中的程式碼,大家並沒有什麼共識。所以很可能在專案中會出現這種風格不一致的程式碼:
75 |
76 | ``` ruby
77 | def add_friend
78 | end
79 |
80 | def remove_friend
81 | end
82 |
83 | def create_post
84 | end
85 |
86 | def delete_post
87 | end
88 |
89 | ```
90 | 透過 REST 的包裝(對於 resource 的操作),可以變得更簡潔直觀,且具有統一的寫法。
91 |
92 | ``` ruby
93 | class FriendShipController
94 | def create
95 | end
96 |
97 | def destroy
98 | end
99 | end
100 |
101 | class PostController
102 | def create
103 | end
104 |
105 | def destroy
106 | end
107 | end
108 |
109 | ```
110 |
111 | ### 介面統一:Action as Resource
112 |
113 | 大眾最常對 REST 產生的一個誤解,就是以為 resource 只有指的是 data。其實 resource 指的是:data + representation (表現形式,如 html, xml, json 等)。
114 |
115 | 在網頁開發中,網站所需要的表現格式,不只有 HTML 而已,有時候也需要提供 json 或者 xml。Rails 透過 responder 實現了
116 |
117 | 在 Resource Oriented Architecture (ROA,一組 REST 架構實現 guideline) 中有一條: `A resource can use file extension in the URI, instead of Content-Type negotiation `。
118 |
119 | Rails 透過 responder 的設計,讓一個 action 可以以 file extension ( .json, .xml, .csv ...etc.) 的形式,提供不同類型的 representation。
120 |
121 | ``` ruby
122 | class PostsController < ApplicationController
123 | # GET /posts
124 | # GET /posts.xml
125 | def index
126 | @posts = Post.all
127 |
128 | respond_to do |format|
129 | format.html # index.html.erb
130 | format.xml { render :xml => @posts }
131 | end
132 | end
133 |
134 | ```
135 |
136 |
137 |
138 | ### 開發速度:透過 CRUD 七個 action + HTTP 四個 verb + 表單 Helper 與 URL Helper 達到高速開發
139 |
140 | REST 之所以能簡化開發,是因為其所引入的架構約束。Rails 中的 REST implementation 將 controller 的 method 限制在七個:
141 |
142 | * index
143 | * show
144 | * new
145 | * edit
146 | * create
147 | * update
148 | * destroy
149 |
150 | 實際上就是整個 CRUD。而在實務上,web services 的需要的操作行為,其實也不脫這七種。Rails 透過 HTTP 作為 generic connector interface,使用 HTTP 的四種 verb : GET、POST、PUT、DELETE 對資源進行操作。
151 |
152 | {::pagebreak :/}
153 |
154 | #### GET
155 |
156 | ``` ruby
157 | <%= link_to("List", posts_path) %>
158 | <%= link_to("Show", post_path(post)) %>
159 | <%= link_to("New", new_post_path) %>
160 | <%= link_to("Edit", edit_post_path(post)) %>
161 | ```
162 |
163 | #### POST
164 | ``` ruby
165 | <%= form_for @post , :url => posts_path , :html => {:method => :post} do |f| %>
166 | ```
167 |
168 | #### PUT
169 | ``` ruby
170 | <%= form_for @post , :url => post_path(@post) , :html => {:method => :put} do |f| %>
171 | ```
172 |
173 | #### Destroy
174 | ``` ruby
175 | <%= link_to("Destroy", post_path(@post), :method => :delete )
176 | ```
177 |
178 | {::pagebreak :/}
179 |
180 | #### Form 綁定 Model Attribute 的設計
181 |
182 | Rails 的表單欄位,是對應 Model Attribute 的:
183 |
184 | ``` ruby
185 | New post
186 |
187 | <%= form_for @post , :url => posts_path do |f| %>
188 | <%= f.error_messages %>
189 | <%= f.text_field :subject %>
190 | <%= f.text_area :content %>
191 | <%= f.submit "Submit", :disable_with => 'Submiting...' %>
192 | <% end -%>
193 |
194 |
195 | <%= link_to 'Back', posts_path %>
196 | ```
197 |
198 | 透過 form_for 傳送出來的表單,會被壓縮包裝成一個 parameter: params[:post],
199 |
200 | ``` ruby
201 | def create
202 | @post = Post.new(params[:post])
203 | if @post.save
204 | flash[:notice] = 'Post was successfully created.'
205 | redirect_to post_path(@post)
206 | else
207 | render :action => "new"
208 | end
209 | end
210 | ```
211 |
212 | 如此一來,原本創造新資源的一連串繁複動作就可以大幅的被簡化,開發速度達到令人驚艷的地步。
213 |
214 | ### 參考資料:
215 |
216 | * [百度百科:REST](http://baike.baidu.com/view/1077487.htm)
217 | * [Wikipedia: REST](http://zh.wikipedia.org/wiki/REST)
218 | * [Wikipedia: ROA](http://en.wikipedia.org/wiki/Resource-oriented_architecture)
219 | * [DHH: Discovering a world of Resource on Rails](http://en.wikipedia.org/wiki/Resource-oriented_architecture)
220 | * [ihower: Rails RESTful 制約即解放](http://ihower.tw/blog/archives/1566)
221 | * [ihower: 什麼是 REST 與 RESTful](http://ihower.tw/blog/archives/1542)
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-0-extra-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## 補充章節:More about RESTful on Rails
4 |
5 |
6 | 有時候開發中也會出現超過這七種 action 之外的動作。那要如何整合進 RESTful 的 route 中呢?
7 |
8 | ### Collection & Member
9 |
10 | #### Member
11 |
12 | 宣告這個動作是屬於單個資源的 URI。可以透過 `/photos/1/preview` 進行 GET。有 `preview_photo_path(photo)` 或 `preview_photo_url(photo)` 這樣的 Url Helper 可以用。
13 |
14 | ``` ruby
15 | resources :photos do
16 | member do
17 | get 'preview'
18 | end
19 | end
20 | ```
21 |
22 | 也可以使用這種寫法:
23 |
24 | ``` ruby
25 | resources :photos do
26 | get 'preview', :on => :member
27 | end
28 | ```
29 |
30 | #### Collection
31 |
32 | 宣告這個動作是屬於一組資源的 URI。可以透過 `/photos/search` 進行 GET。有 `search_photos_path` 或 `search_photos_url` 這樣的 Url Helper 可以用。
33 |
34 |
35 | ``` ruby
36 | resources :photos do
37 | collection do
38 | get 'search'
39 | end
40 | end
41 | ```
42 |
43 | {::pagebreak :/}
44 |
45 | 也可以使用這種寫法:
46 |
47 | ``` ruby
48 | resources :photos do
49 | get 'search', :on => :collection
50 | end
51 | ```
52 |
53 | ### Nested Resources
54 |
55 | 有時候我們會需要使用雙重 resources 來表示一組資源。比如說 Post 與 Comment :
56 |
57 | ``` ruby
58 | resources :posts do
59 | resources :comments
60 | end
61 | ```
62 |
63 | 可以透過 `/posts/123/comments` 進行 GET。有 `post_comments_path(post)` 或 `post_comments_url(post)` 這樣的 Url Helper 可以用。
64 |
65 | 而這樣的 URL 也很清楚的表明,這組資源就是用來存取 編號 123 的 post 下所有的 comments。
66 |
67 | 而要拿取 Post 的 id 可以透過這樣的方式取得:
68 |
69 | ``` ruby
70 | class CommentsController < ApplicationController
71 |
72 | before_filter :find_post
73 |
74 | def index
75 | @comments = @post.comments
76 | end
77 |
78 | protected
79 |
80 | def find_post
81 | @post = Post.find(params[:post_id])
82 | end
83 | end
84 | ```
85 |
86 | {::pagebreak :/}
87 |
88 | ### Namespace Resources
89 |
90 | 但是像 /admin/posts/1/edit 這種 URL,用 Nested Resources 應該造不出來吧?沒錯,這樣的 URL 應該要使用 Namespace 去建構:
91 |
92 | ``` ruby
93 | namespace :admin do
94 | # Directs /admin/products/* to Admin::PostsController
95 | # (app/controllers/admin/posts_controller.rb)
96 | resources :posts
97 | end
98 | ```
99 | 可以透過 `/admin/posts/123/edit` 進行 GET。有 `edit_admin_post_path(post)` 或 `edit_admin_post_url(post)` 這樣的 Url Helper 可以用。
100 |
101 | ### 延伸閱讀
102 |
103 | * [Rails Guide: Rails Routing from the Outside In](http://guides.rubyonrails.org/routing.html)
104 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-0-extra.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1 (補充) RESTful
4 |
5 |
6 | Rails 在 1.2 版本的時候引進了 RESTful 這一套設計風格。而在 2.0 版強迫正式成為開發預設值。
7 |
8 | 說到 RESTful,幾乎每個剛踏進 Rails 框架中的開發者都皺起眉頭。幾乎沒有人能夠在初學階段搞的懂這是什麼玩意。
9 |
10 | 偏偏初學者往往有一個「死扣細節」的習慣:沒「弄懂」就「不敢用」,結果在第一個關卡挫折感就堆的如山高,把玩 RESTful 後打退堂鼓,從此放棄 Rails。
11 |
12 | ### 想學 Rails 就先把 RESTful 背起來
13 |
14 | RESTful 是 Rails 裡面使用的最基礎最頻繁的一個設計手法,偏偏它也是最難一次講解清楚讓剛入門者理解的一個主題。因為在傳統 web 應用程式開發流程中,根本沒有這樣的概念。更別提在市面上這麼多網頁框架,只有 Rails 將之視為預設值。
15 |
16 | 別說是初學者,就連筆者和一些 Rails 界的前輩,在 Rails 剛納入 RESTful 為預設風格時,也沒能通透其中原理。
17 |
18 | 但是不熟練 RESTful 的開發架構,在開發 Rails 時幾乎會寸步難行。在訓練新 Developer 時如何讓他短時間就熟練便上手呢?
19 |
20 | 方法很簡單:強迫他背起來,然後重複寫十遍。
21 |
22 | 聽起來很唬爛,但其實真的紮紮實實寫過十遍以後。這時候再重新講解一次 RESTful 的概念,原本模糊的概念一下就變得豁然開朗了。
23 |
24 | {::pagebreak :/}
25 |
26 | ### 初步熟悉使用 Rails RESTful
27 |
28 | #### 在 config/routes.rb 內加入 resources
29 |
30 |
31 | 在 config/routes.rb 加入 `resources :groups`,就是具體在 rails 對一個 controller 實作 RESTful 的方式(對一個 controller 宣告成為 resources)。
32 |
33 | ~~~~~~~~
34 |
35 | Groupme::Application.routes.draw do
36 | resources :groups
37 | end
38 |
39 | ~~~~~~~~
40 |
41 | 
42 |
43 |
44 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-0.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1 建立 Group
4 |
5 | ### 建立 Group 這個 model
6 |
7 | 執行
8 |
9 | `rails g model group title:string description:text`
10 |
11 | ~~~~~~~~
12 | invoke active_record
13 | create db/migrate/20130529180541_create_groups.rb
14 | create app/models/group.rb
15 | invoke test_unit
16 | create test/models/group_test.rb
17 | create test/fixtures/groups.yml
18 | ~~~~~~~~
19 |
20 | 生成 Group 這個 model。
21 |
22 | 然後跑 `rake db:migrate`。
23 |
24 | ~~~~~~~~
25 | == CreateGroups: migrating ===================================================
26 | -- create_table(:groups)
27 | -> 0.1951s
28 | == CreateGroups: migrated (0.1952s) ==========================================
29 | ~~~~~~~~
30 |
31 |
32 | {::pagebreak :/}
33 |
34 | ### 建立 groups 這個 controller
35 |
36 | 執行 `rails g controller groups`
37 |
38 | ~~~~~~~~
39 |
40 | create app/controllers/groups_controller.rb
41 | invoke erb
42 | create app/views/groups
43 | invoke test_unit
44 | create test/controllers/groups_controller_test.rb
45 | invoke helper
46 | create app/helpers/groups_helper.rb
47 | invoke test_unit
48 | create test/helpers/groups_helper_test.rb
49 | invoke assets
50 | invoke coffee
51 | create app/assets/javascripts/groups.js.coffee
52 | invoke scss
53 | create app/assets/stylesheets/groups.css.scss
54 | ~~~~~~~~
55 |
56 |
57 | ### 把 groups 加入 routing
58 |
59 | 到 `config/routes.rb` 加入
60 |
61 | ~~~~~~~~~
62 | resources :groups
63 | ~~~~~~~~~
64 |
65 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.1 建立 Groups Controller 裡的 index
4 |
5 | 到 `app/controllers/groups_controller.rb` 加入
6 |
7 | ~~~~~~~~~
8 | def index
9 | @groups = Group.all
10 | end
11 |
12 | ~~~~~~~~~
13 |
14 | {::pagebreak :/}
15 |
16 | #### 補上 view:
17 |
18 | `touch app/views/groups/index.html.erb`
19 |
20 | ~~~~~~~~
21 |
22 |
23 |
24 | <%= link_to("New group", new_group_path , :class => "btn btn-mini btn-primary pull-right" ) %>
25 |
26 |
27 |
28 |
29 | # |
30 | Title |
31 | Description |
32 |
33 |
34 |
35 |
36 | <% @groups.each do |group| %>
37 |
38 | # |
39 | <%= link_to(group.title, group_path(group)) %> |
40 | <%= group.description %> |
41 | <%= link_to("Edit", edit_group_path(group), :class => "btn btn-mini" ) %>
42 | <%= link_to("Delete", group_path(group), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %>
43 | |
44 |
45 | <% end %>
46 |
47 |
48 |
49 |
50 | ~~~~~~~~
51 |
52 |
53 |
54 | {::pagebreak :/}
55 |
56 |
57 | ### 解說
58 |
59 |
60 | 通常會放置列表。因此 Group.all 是拉出 group 這個 model 所有的資料。
61 |
62 | ~~~~~~~~
63 |
64 | def index
65 | @groups = Group.all
66 | end
67 |
68 | ~~~~~~~~
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.2 建立 Groups Controller 裡的 show
4 |
5 | 在 `app/controllers/groups_controller.rb` 加入 `show` 這個 action
6 |
7 | ~~~~~~~~
8 | def show
9 | @group = Group.find(params[:id])
10 | end
11 | ~~~~~~~~
12 |
13 |
14 | ### 補上 view
15 |
16 | touch app/views/groups/show.html.erb
17 |
18 | 加入
19 |
20 | ~~~~~~~~
21 |
22 |
23 |
24 |
25 |
26 | <%= link_to("Edit", edit_group_path(@group) , :class => "btn btn-mini btn-primary pull-right" ) %>
27 |
28 |
29 |
<%= @group.title %>
30 |
31 |
<%= @group.description %>
32 |
33 |
34 |
35 | ~~~~~~~~
36 |
37 | {::pagebreak :/}
38 |
39 | ### 解說
40 |
41 |
42 | 秀出單筆資料。 Group.find(123) 是指找 Post model 裡 id 為 123 的資料。http://groupme.dev/groups/123 的 groups 是 controller、show 是 action;如果在 route 裡面沒有特別指定,則 123 通常就是 params[:id] 。
43 |
44 | ~~~~~~~~
45 |
46 | def show
47 | @group = Group.find(params[:id])
48 | end
49 |
50 | ~~~~~~~~
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.3 建立 Groups Controller 裡的 new
4 |
5 | 在 `app/controllers/groups_controller.rb` 加入 `new` 這個 action
6 |
7 | ~~~~~~~~
8 | def new
9 | @group = Group.new
10 | end
11 | ~~~~~~~~
12 |
13 | 解說:建立一個新的 Group object。
14 |
15 | ### 補上 view
16 |
17 | touch `app/views/groups/new.html.erb`
18 |
19 | 加入
20 |
21 | ~~~~~~~~
22 |
23 |
24 |
25 |
26 | <%= simple_form_for @group do |f| %>
27 | <%= f.input :title, :input_html => { :class => "input-xxlarge"} %>
28 | <%= f.input :description, :input_html => { :class => "input-xxlarge"} %>
29 |
30 |
31 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %>
32 |
33 | <% end %>
34 |
35 |
36 |
37 | ~~~~~~~~
38 |
39 | {::pagebreak :/}
40 |
41 |
42 | ### 解說
43 |
44 | initial 一個新的 Post object。
45 |
46 | ~~~~~~~~
47 | def new
48 | @group = Group.new
49 | end
50 | ~~~~~~~~
51 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-4-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.4 (補充) Strong Parameters
4 |
5 | 還記得 2012 年初開發圈曾經騰動一時的: [Github 被入侵事件](http://blog.xdite.net/posts/2012/03/05/github-hacked-rails-security/) 嗎?
6 |
7 | 事件的起因是因為 Rails 內建的 mass assignment,無法容易保護資料的安全性。而原先內建的 attr_accessible / attr_protected 的設計並不足夠實務使用(無法強制 Developer 使用)。
8 |
9 | 在該事件發生後,Rails 核心團隊在 3.2.3 之後的版本,都開啟了 config.whitelist_attributes = true 的選項作為預設。也就是專案自動會對所有的 model 都自動開啟白名單模式,你必須手動對每一個 model 都加上 attr_accessible。這樣表單送值才會有辦法運作。
10 |
11 | 此舉好處是:「夠安全」,能強迫開發者在設計表單時記得審核 model 該欄位是否適用於 mass-assign。但這樣的機制也引發開發者「不實用」「找麻煩」的議論。
12 |
13 | ### 臨時 Hack 找麻煩
14 |
15 | 首先會遇到的第一個問題是:「新手容易踩中地雷」
16 |
17 | 首先最麻煩的當然是,新手會被這一行設定整到。新手不知道此機制為何而來,出了問題也不知道如何關掉這個設定。更麻煩的是撰寫新手教學的人,必須又花上一大篇幅解釋 mass-assignment 的設計機制,為何重要,為何新手需要重視…etc.
18 |
19 | 第二個問題: 「不是很實用」
20 |
21 | 手動一個一個加上 attr_accessible 真的很煩人,因為這也表示,若新增一個欄位,開發者也要手動去加上 attr_accessible,否則很可能在某些表單直接出現異常現象。
22 |
23 | 而最麻煩的還是,其實 attr_accessible 不敷使用,因為一個系統通常存在不只一種角色,普通使用與 Admin 需要的 mass-assignment 範圍絕對不盡相同。
24 |
25 | 雖然 Rails 在 3.1 加入了 scoped mass assignment。但這也只能算是 model 方面的解決手法。一旦系統內有更多其他流程需求,scoped mass assignment 的設計頓時就不夠解決問題了…
26 |
27 | {::pagebreak :/}
28 |
29 | ### 癥結點:欄位核准與否應該由 controller 管理,而非 model
30 |
31 | 大家戰了一陣子,終於收斂出一個結論。一切的癥結點在於之前的設計想法都走錯了方向,欄位核准與否應該由 controller 決定。因為「流程需求」本來就應該作在 controller 裡面。新的 solution [strong_parameters](https://github.com/rails/strong_parameters/) 就是這個解法。
32 |
33 | 在當時:Rails 之父 DHH 提供了他的最佳實務:
34 |
35 |
36 | ~~~~~~~~
37 |
38 | class PostsController < ActionController::Base
39 | def create
40 | Post.create(post_params)
41 | end
42 |
43 | def update
44 | Post.find(params[:id]).update_attributes!(post_params)
45 | end
46 |
47 | private
48 | def post_params
49 | params[:post].slice(:title, :content)
50 | end
51 | end
52 | ~~~~~~~~
53 |
54 | 使用 slice 去把真正需要的部分切出來,所以就算 hacker 打算送其他 parameter 也會被過濾掉(不會有 exception)。
55 |
56 | {::pagebreak :/}
57 |
58 | ### Strong Parameters 的作法
59 |
60 | 而 strong_parameters 的作法是必須過一段 permit,允許欄位。如果送不允許的欄位進來,會 throw exception。
61 |
62 | ~~~~~~~~
63 | class PeopleController < ActionController::Base
64 | def update
65 | person.update_attributes!(person_params)
66 | redirect_to :back
67 | end
68 |
69 | private
70 | def person_params
71 | params.require(:person).permit(:name, :age)
72 | end
73 | end
74 | ~~~~~~~~
75 |
76 | ### 進階 Strong Parameters
77 |
78 | 當然,每一段 controller 都要來上這麼一段,有時候也挺煩人的。Railscast 也整理了一些[進階招數](http://railscasts.com/episodes/371-strong-parameters):
79 |
80 | * Nested Attributes
81 | * Orgngized to Class
82 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-4.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.4 建立 Groups Controller 裡的 create
4 |
5 | 在 `app/controllers/groups_controller.rb` 加入 `create` 這個 action
6 |
7 | ~~~~~~~~
8 |
9 | def create
10 | @group = Group.new(params[:group])
11 | @group.save
12 |
13 | redirect_to groups_path
14 | end
15 |
16 |
17 | ~~~~~~~~
18 |
19 | 不過此時,我們發現好像少了一種實際狀況?如果一個 Group 沒有 title,應該算是不合法的 group 吧?
20 |
21 | 我們應該要限制沒有輸入 title 的 group,必須要退回到 `new` 這個表單。
22 |
23 |
24 | 修改 `app/models/group.rb`,限制 group 一定要有標題
25 |
26 | ~~~~~~~~
27 |
28 | class Group < ActiveRecord::Base
29 |
30 | validates :title, :presence => true
31 |
32 | end
33 |
34 | ~~~~~~~~
35 |
36 | 同時修改 `app/controllers/groups_controller.rb` 中 `create` 這個 action
37 |
38 | 改成以下內容
39 |
40 | ~~~~~~~~
41 |
42 | def create
43 | @group = Group.new(group_params)
44 |
45 | if @group.save
46 | redirect_to groups_path
47 | else
48 | render :new
49 | end
50 | end
51 |
52 |
53 | private
54 |
55 |
56 | def group_params
57 | params.require(:group).permit(:title, :description)
58 | end
59 |
60 | ~~~~~~~~
61 |
62 |
63 |
64 | {::pagebreak :/}
65 |
66 |
67 | ### 解說
68 |
69 | 這邊要搭配 `app/views/groups/new.html.erb` 這個 view 並列一起看。
70 |
71 | ~~~~~~~~
72 |
73 |
74 |
75 |
76 | <%= simple_form_for @group do |f| %>
77 | <%= f.input :title, :input_html => { :class => "input-xxlarge"} %>
78 | <%= f.input :description, :input_html => { :class => "input-xxlarge"} %>
79 |
80 |
81 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %>
82 |
83 | <% end %>
84 |
85 |
86 |
87 | ~~~~~~~~
88 |
89 |
90 | 在 RESTful Rails 的寫法中,對 `groups_path` 丟 POST 就是對應到 create 的動作。而 Rails 在設計上,form 是綁 model 的,因此整個 form 的內容會被包成一個 hash,在這裡就是 params[:group]。create action 初始一個 object ,並把 params[:group] 整包塞進這個 object 裡。如果 @group 能夠成功的儲存,就「重導」到 index action ,失敗則「退回」到 new action 。
91 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-5.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.5 建立 Groups Controller 裡的 edit
4 |
5 | 在 `app/controllers/groups_controller.rb` 加入 `edit` 這個 action
6 |
7 | ~~~~~~~~
8 | def edit
9 | @group = Group.find(params[:id])
10 | end
11 | ~~~~~~~~
12 |
13 | 解說: query 出指定的Group model object,然後進行編輯。
14 |
15 | ### 補上 view
16 |
17 | touch app/views/groups/edit.html.erb
18 |
19 | 加入
20 |
21 | ~~~~~~~~
22 |
23 |
24 |
25 |
26 | <%= simple_form_for @group do |f| %>
27 | <%= f.input :title, :input_html => { :class => "input-xxlarge"} %>
28 | <%= f.input :description, :input_html => { :class => "input-xxlarge"} %>
29 |
30 |
31 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %>
32 |
33 | <% end %>
34 |
35 |
36 |
37 | ~~~~~~~~
38 |
39 |
40 | {::pagebreak :/}
41 |
42 | ### 解說
43 |
44 | query 出指定的 Group model object,然後進行編輯。
45 |
46 | ~~~~~~~~
47 | def edit
48 | @group = Group.find(params[:id])
49 | end
50 | ~~~~~~~~
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-6-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.6 (補充) update_attributes 與 update
4 |
5 | 在 Rails 4.0.0 前的 update 表單一向是使用 `update_attributes` 這個 API。而到了 Rails 4 之後改用 update。其最大的原因是因為 Security 架構改變。這部份的歷史因素可以參考 Ch 1.1.4。
6 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-6.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.6 建立 Groups Controller 裡的 update
4 |
5 |
6 | 在 `app/controllers/groups_controller.rb` 加入 `update` 這個 action
7 |
8 | ~~~~~~~~
9 | def update
10 | @group = Group.find(params[:id])
11 |
12 | if @group.update(group_params)
13 | redirect_to group_path(@group)
14 | else
15 | render :edit
16 | end
17 | end
18 | ~~~~~~~~
19 |
20 | ### 解說
21 |
22 | 這邊也要翻回 app/views/posts/edit.html.erb 這個 view 一起來看。
23 |
24 | ~~~~~~~~
25 |
26 |
27 |
28 |
29 | <%= simple_form_for @group do |f| %>
30 | <%= f.input :title, :input_html => { :class => "input-xxlarge"} %>
31 | <%= f.input :description, :input_html => { :class => "input-xxlarge"} %>
32 |
33 |
34 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %>
35 |
36 | <% end %>
37 |
38 |
39 |
40 | ~~~~~~~~
41 |
42 | 在 RESTful Rails 的寫法中,對 `group_path` 丟 PUT 就是對應到 update 的動作。form 會背包成一個 hash,如果 @group 能夠吃進 params[:group] 進行更新且成功儲存,就會「重導」到 show action,失敗則「退回」到 edit action。
43 |
--------------------------------------------------------------------------------
/manuscript/chapter-01-1-7.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 1.1.7 建立 Groups Controller 裡的 destroy
4 |
5 | 在 `app/controllers/groups_controller.rb` 加入 `destroy` 這個 action
6 |
7 | ~~~~~~~~
8 | def destroy
9 | @group = Group.find(params[:id])
10 |
11 | @group.destroy
12 |
13 | redirect_to groups_path
14 | end
15 | ~~~~~~~~
16 |
17 |
18 | 至此,我們完成了 Group 的 CRUD。
19 |
20 | ### 把首頁改成 Group 的 Index 頁
21 |
22 | 最後修改 `config/routes.rb`,把 root 指向 group 的 index 頁
23 |
24 | `root :to => "groups#index"`
25 |
26 |
27 | {::pagebreak :/}
28 |
29 | ### 解說
30 |
31 |
32 | 這邊要翻回 app/views/posts/index.html.erb 這個 view 一起來看。
33 |
34 | ~~~~~~~~
35 |
36 |
37 | # |
38 | <%= link_to(group.title, group_path(group)) %> |
39 | <%= group.description %> |
40 | <%= link_to("Edit", edit_group_path(group), :class => "btn btn-mini" ) %>
41 | <%= link_to("Delete", group_path(group), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %>
42 | |
43 |
44 | ~~~~~~~~
45 |
46 |
47 | 找到該筆資料並刪除之。在 RESTful Rails 的寫法中,對 `group_path` 丟一個 DELETE,就會對應到 destroy 的動作。可看一下 `link_to "Destroy"`那一行。
--------------------------------------------------------------------------------
/manuscript/chapter-02-1-0.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 2.1 建立 Post
4 |
5 | ### 建立 Post 這個 model
6 |
7 | 執行
8 |
9 | `rails g model post content:text group_id:integer`
10 |
11 | ~~~~~~~~
12 | invoke active_record
13 | create db/migrate/20130529200707_create_posts.rb
14 | create app/models/post.rb
15 | invoke test_unit
16 | create test/models/post_test.rb
17 | create test/fixtures/posts.yml
18 | ~~~~~~~~
19 |
20 | 生成 Post 這個 model。
21 |
22 | 然後跑 `rake db:migrate`。
23 |
24 | ~~~~~~~~
25 | == CreatePosts: migrating ====================================================
26 | -- create_table(:posts)
27 | -> 0.1297s
28 | == CreatePosts: migrated (0.1298s) ===========================================
29 | ~~~~~~~~
30 |
31 |
32 | {::pagebreak :/}
33 |
34 | ### 建立 posts 這個 controller
35 |
36 | 執行 `rails g controller posts`
37 |
38 | ~~~~~~~~
39 | create app/controllers/posts_controller.rb
40 | invoke erb
41 | create app/views/posts
42 | invoke test_unit
43 | create test/controllers/posts_controller_test.rb
44 | invoke helper
45 | create app/helpers/posts_helper.rb
46 | invoke test_unit
47 | create test/helpers/posts_helper_test.rb
48 | invoke assets
49 | invoke coffee
50 | create app/assets/javascripts/posts.js.coffee
51 | invoke scss
52 | create app/assets/stylesheets/posts.css.scss
53 | ~~~~~~~~
54 |
55 |
56 | ### 把 posts 加入 routing
57 |
58 | 到 `config/routes.rb` 修改原有的
59 |
60 | ~~~~~~~~~
61 | resources :groups
62 | ~~~~~~~~~
63 |
64 | 變成
65 |
66 |
67 | ~~~~~~~~~
68 | resources :groups do
69 | resources :posts
70 | end
71 | ~~~~~~~~~
72 |
73 | {::pagebreak :/}
74 |
75 |
76 | ### 加入 Group 與 Post 的關聯
77 |
78 | 修改 `app/models/group.rb` 加入 `has_many :posts`
79 |
80 | ~~~~~~~~~
81 | class Group < ActiveRecord::Base
82 |
83 | has_many :posts
84 | validates :title, :presence => true
85 |
86 | end
87 | ~~~~~~~~~
88 |
89 | 修改 `app/models/post.rb` 加入 `belongs_to :group`
90 |
91 | ~~~~~~~~~
92 | class Post < ActiveRecord::Base
93 | belongs_to :group
94 | end
95 |
96 | ~~~~~~~~~
97 |
98 |
99 |
--------------------------------------------------------------------------------
/manuscript/chapter-02-1-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 2.1.1 在 Groups controller 的 show 裡面撈出相關的 Post
4 |
5 | 到 `app/controllers/groups_controller.rb`,將 show 修改成以下內容
6 |
7 | ~~~~~~~~~
8 | def show
9 | @group = Group.find(params[:id])
10 | @posts = @group.posts
11 | end
12 |
13 | ~~~~~~~~~
14 |
15 | {::pagebreak :/}
16 |
17 | #### 補上 view:
18 |
19 | 修改 `app/views/groups/show.html.erb`
20 |
21 | ~~~~~~~~
22 |
23 |
24 |
25 | <%= link_to("Edit", edit_group_path(@group) , :class => "btn btn-mini ")%>
26 |
27 | <%= link_to("New Post", new_group_post_path(@group) , :class => "btn btn-mini btn-primary")%>
28 |
29 |
30 |
<%= @group.title %>
31 |
<%= @group.description %>
32 |
33 |
34 |
35 |
36 |
37 | <% @posts.each do |post| %>
38 |
39 | <%= post.content %> |
40 | <%= link_to("Edit", edit_group_post_path(post.group, post), :class => "btn btn-mini")%>
41 | <%= link_to("Delete", group_post_path(post.group, post), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %> |
42 |
43 | <% end %>
44 |
45 |
46 |
47 |
48 |
49 | ~~~~~~~~
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/manuscript/chapter-02-1-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 2.1.2 建立 Posts Controller 裡的 new
4 |
5 | 在 `app/controllers/posts_controller.rb` 加入 `new` 這個 action
6 |
7 | ~~~~~~~~
8 | def new
9 | @group = Group.find(params[:group_id])
10 | @post = @group.posts.build
11 | end
12 | ~~~~~~~~
13 |
14 |
15 | ### 補上 view
16 |
17 | `touch app/views/posts/new.html.erb`
18 |
19 | 加入
20 |
21 | ~~~~~~~~
22 |
23 |
24 |
25 | <%= simple_form_for [@group,@post] do |f| %>
26 | <%= f.input :content, :input_html => { :class => "input-xxlarge"} %>
27 |
28 |
29 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %>
30 |
31 | <% end %>
32 |
33 |
34 |
35 | ~~~~~~~~
36 |
37 |
--------------------------------------------------------------------------------
/manuscript/chapter-02-1-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 2.1.3 建立 Posts Controller 裡的 create
4 |
5 | 在 `app/controllers/posts_controller.rb` 加入 `create` 這個 action
6 |
7 | ~~~~~~~~
8 |
9 | def create
10 | @group = Group.find(params[:group_id])
11 | @post = @group.posts.new(post_params)
12 |
13 | if @post.save
14 | redirect_to group_path(@group)
15 | else
16 | render :new
17 | end
18 | end
19 |
20 | private
21 |
22 |
23 | def post_params
24 | params.require(:post).permit(:content)
25 | end
26 |
27 | ~~~~~~~~
28 |
29 | 不過此時,我們發現 Post 其實也需要被驗證。如果一個 Post 沒有 content,應該也算是不合法的 post?
30 |
31 | 我們應該要限制沒有輸入 content 的 post,必須要退回到 `new` 這個表單。
32 |
33 |
34 | 修改 `app/models/post.rb`,限制 post 一定要有內容
35 |
36 | ~~~~~~~~
37 |
38 | class Post < ActiveRecord::Base
39 |
40 | belongs_to :group
41 | validates :content, :presence => true
42 |
43 | end
44 |
45 | ~~~~~~~~
46 |
47 |
48 | {::pagebreak :/}
49 |
50 |
51 | ### 解說
52 |
53 | Rails 的 ORM 內建一系列 Validation 的 API,可以幫助 Developer 快速驗證資料的類型與格式。
54 |
55 |
56 |
--------------------------------------------------------------------------------
/manuscript/chapter-02-1-4.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 2.1.4 建立 Posts Controller 裡的 edit
4 |
5 | 在 `app/controllers/groups_controller.rb` 加入 `edit` 這個 action
6 |
7 | ~~~~~~~~
8 | def edit
9 | @group = Group.find(params[:group_id])
10 | @post = @group.posts.find(params[:id])
11 | end
12 |
13 | ~~~~~~~~
14 |
15 |
16 | ### 補上 view
17 |
18 | touch app/views/posts/edit.html.erb
19 |
20 | ~~~~~~~~
21 |
22 |
23 |
24 | <%= simple_form_for [@group,@post] do |f| %>
25 | <%= f.input :content, :input_html => { :class => "input-xxlarge"} %>
26 |
27 |
28 | <%= f.submit "Submit", :disable_with => 'Submiting...', :class => "btn btn-primary" %>
29 |
30 | <% end %>
31 |
32 |
33 |
34 | ~~~~~~~~
35 |
--------------------------------------------------------------------------------
/manuscript/chapter-02-1-5.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 2.1.5 建立 Posts Controller 裡的 update
4 |
5 |
6 | 在 `app/controllers/posts_controller.rb` 加入 `update` 這個 action
7 |
8 | ~~~~~~~~
9 | def update
10 | @group = Group.find(params[:group_id])
11 | @post = @group.posts.find(params[:id])
12 |
13 | if @post.update(post_params)
14 | redirect_to group_path(@group)
15 | else
16 | render :edit
17 | end
18 | end
19 | ~~~~~~~~
20 |
--------------------------------------------------------------------------------
/manuscript/chapter-02-1-6.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 2.1.6 建立 Posts Controller 裡的 destroy
4 |
5 | 在 `app/controllers/groups_controller.rb` 加入 `destroy` 這個 action
6 |
7 | ~~~~~~~~
8 | def destroy
9 | @group = Group.find(params[:group_id])
10 | @post = @group.posts.find(params[:id])
11 |
12 | @post.destroy
13 |
14 | redirect_to group_path(@group)
15 | end
16 | ~~~~~~~~
17 |
18 |
19 | 至此,我們完成了 Post 的 CRUD。
20 |
--------------------------------------------------------------------------------
/manuscript/chapter-02-1-7.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 2.1.7 以 before_action 整理重複的程式碼
4 |
5 | 有沒有覺得 Posts 裡的每個 action 裡面都有一行
6 |
7 | ~~~~~~~~
8 | @group = Group.find(params[:group_id])
9 | ~~~~~~~~
10 |
11 | 很冗餘呢?
12 |
13 | 我們可以利用 `before_action` 這個技巧,把重複的程式碼去掉:
14 |
15 | 首先在 private 底下,新增一個 `find_group` method
16 |
17 |
18 | ~~~~~~~~
19 | def find_group
20 | @group = Group.find(params[:group_id])
21 | end
22 | ~~~~~~~~
23 |
24 | 然後在 `class PostsController < ApplicationController` 下,加一行
25 |
26 | ~~~~~~~~
27 | before_action :find_group
28 | ~~~~~~~~
29 |
30 | 再把每個 action 的這一行砍掉
31 |
32 | ~~~~~~~~
33 | @group = Group.find(params[:group_id])
34 | ~~~~~~~~
35 |
36 | 最後的成品會長這樣
37 |
38 | ~~~~~~~~
39 | class PostsController < ApplicationController
40 |
41 | before_action :find_group
42 |
43 | def new
44 | @post = @group.posts.build
45 | end
46 |
47 | def create
48 |
49 | @post = @group.posts.new(post_params)
50 |
51 | if @post.save
52 | redirect_to group_path(@group)
53 | else
54 | render :new
55 | end
56 | end
57 |
58 | def edit
59 | @post = @group.posts.find(params[:id])
60 | end
61 |
62 | def update
63 |
64 | @post = @group.posts.find(params[:id])
65 |
66 | if @post.update(post_params)
67 | redirect_to group_path(@group)
68 | else
69 | render :edit
70 | end
71 | end
72 |
73 |
74 | def destroy
75 |
76 | @post = @group.posts.find(params[:id])
77 |
78 | @post.destroy
79 |
80 | redirect_to group_path(@group)
81 | end
82 |
83 |
84 | private
85 |
86 |
87 | def find_group
88 | @group = Group.find(params[:group_id])
89 | end
90 |
91 | def post_params
92 | params.require(:post).permit(:content)
93 | end
94 | end
95 |
96 | ~~~~~~~~
97 |
98 | {::pagebreak :/}
99 |
100 | ### 解說
101 |
102 | before_action 是一個常見的 controller 技巧,用來收納重複的程式碼。
103 |
104 | before_action 可以用 only,指定某些 action 執行:
105 |
106 | ~~~~~~~~
107 | before_action :find_group, :only => [:edit, :update]
108 | ~~~~~~~~
109 |
110 | 或者使用 except,排除某些 action 不執行:
111 |
112 | ~~~~~~~~
113 | before_action :find_group, :except => [:show, :index]
114 | ~~~~~~~~
115 |
--------------------------------------------------------------------------------
/manuscript/chapter-02.txt:
--------------------------------------------------------------------------------
1 | # 練習作業 2 - 在 Group 裡面發表文章 - 雙層 RESTFul
2 |
3 | 上一章我們完成了 Group 的 CRUD,這一章的練習目標是在 Group 裡面發表文章。並且文章網址是使用 http://groupme.dev/group/1/post/2 這種網址表示。
4 |
5 | ### 本章練習主題
6 |
7 | * 學會 has_many, belongs_to
8 | * 學會 resources 的設定 ( 雙層 resources )
9 | * 使用 before_action 整理程式碼
10 |
11 |
--------------------------------------------------------------------------------
/manuscript/chapter-03-0.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 3.0 devise 與 Rails 4
4 |
5 | ### 安裝 gem
6 |
7 | Rails 內,安裝 gem 的方式是透過 Bundler 這個工具。具體方式是修改 `Gemfile` 這個檔案,加入所需要安裝的 gem,再執行 `bundle install`,即能安裝完畢。
8 |
9 | ### 安裝 devise
10 |
11 |
12 | [devise](https://github.com/plataformatec/devise) 是目前 Rails 界最被廣為使用的認證系統。其彈性的設計支援很多實務上的需求,如:鎖定賬號(Lockable)、需要認證(Confirmable)、取回帳號(Recoverable)、與第三方認證如 Facebook 整合( OmniAuthable)。
13 |
14 | 在本書裡面我們使用的 Bootstrappers 內建即幫我們安裝好 `devise` 這個 gem。
15 |
16 | 不過你還是可以開一個空專案,實際裝一下練習看看。
17 |
18 |
19 | 修改 `Gemfile`
20 |
21 | ~~~~~~~~~
22 | gem 'devise'
23 | ~~~~~~~~~
24 |
25 | 使用 bundle 安裝
26 |
27 | `bundle install`
28 |
29 | 產生必要檔案
30 |
31 | `rails g devise:install`
32 |
33 | 產生 user model 檔案
34 |
35 | `rails generate devise user`
36 |
37 |
38 | #### devise 相關連結
39 |
40 | * 註冊 `<%= link_to( "Sign Up" ,new_user_registration_path) %> `
41 | * 登入 `<%= link_to( "Login", new_user_session_path ) %> `
42 | * 登出 `<%= link_to("Logout",destroy_user_session_path, :method => :delete ) %> `
43 |
44 |
45 |
46 | ### devise 相關 method
47 |
48 | 1. 判斷現在使用者是否登入了,可以使用 current_user.blank?。
49 | 2. 要取現在這個登入的使用者資料,可以使用 current_user
50 |
51 | {::pagebreak :/}
52 |
53 | ### login_required
54 |
55 | Bootstrappers 在 `app/controller/application_controller.rb` 也先預幫開發者準備好一個 method:`login_required`。
56 |
57 |
58 | ~~~~~~~~~
59 |
60 | def login_required
61 | if current_user.blank?
62 | respond_to do |format|
63 | format.html {
64 | authenticate_user!
65 | }
66 | format.js{
67 | render :partial => "common/not_logined"
68 | }
69 | format.all {
70 | head(:unauthorized)
71 | }
72 | end
73 | end
74 |
75 | end
76 |
77 | ~~~~~~~~~
78 |
79 | 如果開發者只是單純想限制哪一個 action 需要登入才能使用,只要掛上 before_action ,再指定即可。
80 |
81 |
82 | {::pagebreak :/}
83 |
84 | ### 客製化 devise
85 |
86 | 原始的 devise 設計,只有 email 與 password 的設計,並沒有 name 的欄位。雖然 Bootstrappers 幫忙也生了 name 的欄位,但是我們還是得在 devise 的註冊表單加進去 name 才行,否則註冊進去的 user 都會沒有 name。
87 |
88 | devise 的 view 預設是隱藏起來,直接使用 gem 內的 view。要客製化必須要先使用 generator 生出來,再修改複寫。
89 |
90 | 具體的步驟是執行:
91 |
92 | `rails g devise:views`
93 |
94 |
95 | 會生成一堆檔案
96 |
97 | ~~~~~~~~~
98 | invoke Devise::Generators::SharedViewsGenerator
99 | create app/views/devise/shared
100 | create app/views/devise/shared/_links.erb
101 | invoke simple_form_for
102 | create app/views/devise/confirmations
103 | create app/views/devise/confirmations/new.html.erb
104 | create app/views/devise/passwords
105 | create app/views/devise/passwords/edit.html.erb
106 | create app/views/devise/passwords/new.html.erb
107 | create app/views/devise/registrations
108 | create app/views/devise/registrations/edit.html.erb
109 | create app/views/devise/registrations/new.html.erb
110 | create app/views/devise/sessions
111 | create app/views/devise/sessions/new.html.erb
112 | create app/views/devise/unlocks
113 | create app/views/devise/unlocks/new.html.erb
114 | invoke erb
115 | create app/views/devise/mailer
116 | create app/views/devise/mailer/confirmation_instructions.html.erb
117 | create app/views/devise/mailer/reset_password_instructions.html.erb
118 | create app/views/devise/mailer/unlock_instructions.html.erb
119 | ~~~~~~~~~
120 |
121 | {::pagebreak :/}
122 |
123 | ### 修改註冊表單
124 |
125 | 註冊表單的檔案是 `app/views/devise/registrations/new.html.erb`,加入一行 name 使之成為
126 |
127 | ~~~~~~~~~
128 | Sign up
129 |
130 | <%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
131 | <%= f.error_notification %>
132 |
133 |
134 | <%= f.input :email, :required => true, :autofocus => true %>
135 | <%= f.input :name, :required => true %>
136 | <%= f.input :password, :required => true %>
137 | <%= f.input :password_confirmation, :required => true %>
138 |
139 |
140 |
141 | <%= f.button :submit, "Sign up" %>
142 |
143 | <% end %>
144 |
145 | <%= render "users/shared/links" %>
146 | ~~~~~~~~~
147 |
148 | ### 加入 strong_parameters 與 devise 整合的 hack
149 |
150 | 在 `app/controller/application_controller` 加上這些設定:
151 |
152 | ~~~~~~
153 |
154 | before_filter :configure_permitted_parameters, if: :devise_controller?
155 |
156 |
157 | protected
158 |
159 | def configure_permitted_parameters
160 | devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:name, :email, :password, :password_confirmation) }
161 | end
162 |
163 | ~~~~~~
164 |
165 | I>
166 |
167 |
--------------------------------------------------------------------------------
/manuscript/chapter-03-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 3.1 對需要登入才能使用的 Action 加入限制
4 |
5 | 根據需求:使用者必須能夠 註冊 / 登入,登入後才可以開設 Group,與發表 Post。
6 |
7 | 所以我們要在 Groups controller 加入:
8 |
9 | ~~~~~~~~~
10 | class GroupsController < ApplicationController
11 |
12 | before_action :login_required, :only => [:new, :create, :edit,:update,:destroy]
13 | ~~~~~~~~~
14 |
15 | 在 Posts controller 加入:
16 |
17 | ~~~~~~~~~
18 | class PostsController < ApplicationController
19 |
20 | before_action :login_required, :only => [:new, :create, :edit,:update,:destroy]
21 | ~~~~~~~~~
22 |
23 |
24 |
--------------------------------------------------------------------------------
/manuscript/chapter-03-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 3.2 讓 Group 與 User 產生關聯:
4 |
5 | 新增一條 migration: `rails g migration add_user_id_to_group`
6 |
7 | ~~~~~~~~~
8 | invoke active_record
9 | create db/migrate/20130531141923_add_user_id_to_group.rb
10 | ~~~~~~~~~
11 |
12 | 填入以下內容
13 |
14 | ~~~~~~~~~
15 | class AddUserIdToGroup < ActiveRecord::Migration
16 | def change
17 | add_column :groups, :user_id, :integer
18 | end
19 | end
20 | ~~~~~~~~~
21 |
22 | 執行 `rake db:migrate`
23 |
24 | ~~~~~~~~~
25 | == AddUserIdToGroup: migrating ===============================================
26 | -- add_column(:groups, :user_id, :integer)
27 | -> 0.0213s
28 | == AddUserIdToGroup: migrated (0.0214s) ======================================
29 | ~~~~~~~~~
30 |
31 |
32 | 修改 `app/models/user.rb` 加入 `has_many :groups`
33 |
34 | 內容如下
35 |
36 | ~~~~~~~~~
37 | class User < ActiveRecord::Base
38 | # Include default devise modules. Others available are:
39 | # :token_authenticatable, :confirmable,
40 | # :lockable, :timeoutable and :omniauthable
41 |
42 | has_many :groups
43 |
44 | extend OmniauthCallbacks
45 |
46 | devise :database_authenticatable, :registerable,
47 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable
48 |
49 |
50 | end
51 | ~~~~~~~~~
52 |
53 | {::pagebreak :/}
54 |
55 | 修改 `app/models/group.rb` 加入
56 |
57 | ~~~~~~~~~
58 | belongs_to :owner, :class_name => "User", :foreign_key => :user_id
59 |
60 | def editable_by?(user)
61 | user && user == owner
62 | end
63 | ~~~~~~~~~
64 |
65 |
66 | 內容如下:
67 |
68 | ~~~~~~~~~
69 | class Group < ActiveRecord::Base
70 |
71 | belongs_to :owner, :class_name => "User", :foreign_key => :user_id
72 | has_many :posts
73 |
74 | validates :title, :presence => true
75 |
76 |
77 | def editable_by?(user)
78 | user && user == owner
79 | end
80 | end
81 |
82 |
83 | ~~~~~~~~~
84 |
85 | {::pagebreak :/}
86 |
87 | 接著我們要把 Groups 的幾個 action 內容替換掉:
88 |
89 | ### create
90 |
91 | ~~~~~~~~~
92 |
93 | def create
94 | @group = current_user.groups.build(group_params)
95 | if @group.save
96 | redirect_to groups_path
97 | else
98 | render :new
99 | end
100 | end
101 | ~~~~~~~~~
102 |
103 | ### edit
104 |
105 | ~~~~~~~~~
106 | def edit
107 | @group = current_user.groups.find(params[:id])
108 | end
109 | ~~~~~~~~~
110 |
111 |
112 | ### update
113 |
114 | ~~~~~~~~~
115 | def update
116 | @group = current_user.groups.find(params[:id])
117 |
118 | if @group.update(group_params)
119 | redirect_to group_path(@group)
120 | else
121 | render :edit
122 | end
123 | end
124 | ~~~~~~~~~
125 |
126 | {::pagebreak :/}
127 |
128 |
129 | ### destroy
130 |
131 | ~~~~~~~~~
132 | def destroy
133 | @group = current_user.groups.find(params[:id])
134 |
135 | @group.destroy
136 |
137 | redirect_to groups_path
138 | end
139 | ~~~~~~~~~
140 |
141 | {::pagebreak :/}
142 |
143 | 把 app/views/groups/index.html.erb 的內容換掉
144 |
145 | ~~~~~~~~~
146 |
147 |
148 |
149 | <%= link_to("New group", new_group_path , :class => "btn btn-mini btn-primary pull-right")%>
150 |
151 |
152 |
153 | # |
154 | Title |
155 | Description |
156 | Owner |
157 |
158 |
159 |
160 | <% @groups.each do |group| %>
161 |
162 | # |
163 | <%= link_to(group.title, group_path(group)) %> |
164 | <%= group.description %> |
165 | <%= group.owner.name %> |
166 |
167 | <% if current_user && group.editable_by?(current_user) %>
168 | <%= link_to("Edit", edit_group_path(group), :class => "btn btn-mini")%>
169 | <%= link_to("Delete", group_path(group), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %>
170 | <% end %>
171 | |
172 |
173 | <% end %>
174 |
175 |
176 |
177 |
178 | ~~~~~~~~~
179 |
180 | {::pagebreak :/}
181 |
182 | ### 解說
183 |
184 | 在前面一章的例子裡面。我們都是使用 ` @group = Group.find(params[:id])`。
185 |
186 | 但是這樣無法確保這個 action 的安全性。其實這幾個 action 必須是要「限定」本人才能修改的。但是初學者可能會寫出這樣的 code。
187 |
188 | ~~~~~~~~~
189 | def edit
190 | @group = Group.find(params[:id])
191 |
192 | if @group.user != current_user
193 | flash[:warning] = "No Permission"
194 | redirec_to root_path
195 | end
196 |
197 | end
198 | ~~~~~~~~~
199 |
200 | 事實上,我們可以只用 `current_user.groups` 這樣的天然限制。就足以阻擋掉這樣的非法存取了。
201 |
202 | ~~~~~~~~~
203 | def edit
204 | @group = current_user.groups.find(params[:id])
205 | end
206 | ~~~~~~~~~
207 |
208 | 在這個情況下,Rails 在 Production 環境上面會直接跳 404 找不到頁面。直接確保該頁面的安全性。
209 |
210 | 以下的 create 這個例子也是類似的狀況。原先開發者可能會寫出
211 |
212 | ~~~~~~~~~
213 |
214 | def create
215 | @group = Group.new(group_params)
216 | @group.user = current_user
217 | if @group.save
218 | redirect_to groups_path
219 | else
220 | render :new
221 | end
222 | end
223 | ~~~~~~~~~
224 |
225 | {::pagebreak :/}
226 |
227 | 利用內建的 association 就可以改寫成以下範例:
228 |
229 | ~~~~~~~~~
230 |
231 | def create
232 | @group = current_user.groups.build(group_params)
233 | if @group.save
234 | redirect_to groups_path
235 | else
236 | render :new
237 | end
238 | end
239 | ~~~~~~~~~
240 |
241 | 效果是一樣的。
242 |
--------------------------------------------------------------------------------
/manuscript/chapter-03-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 3.3 讓 Post 與 User 產生關聯:
4 |
5 |
6 | 新增一條 migration: `rails g migration add_user_id_to_post`
7 |
8 | ~~~~~~~~~
9 | invoke active_record
10 | create db/migrate/20130531153435_add_user_id_to_post.rb
11 | ~~~~~~~~~
12 |
13 | 填入以下內容
14 |
15 | ~~~~~~~~~
16 | class AddUserIdToPost < ActiveRecord::Migration
17 | def change
18 | add_column :posts, :user_id, :integer
19 | end
20 | end
21 |
22 | ~~~~~~~~~
23 |
24 |
25 | 執行 `rake db:migrate`
26 |
27 | ~~~~~~~~~
28 | == AddUserIdToPost: migrating ================================================
29 | -- add_column(:posts, :user_id, :integer)
30 | -> 0.0176s
31 | == AddUserIdToPost: migrated (0.0177s) =======================================
32 | ~~~~~~~~~
33 |
34 |
35 | 修改 `app/models/user.rb` 加入 `has_many :posts`
36 |
37 |
38 | 內容如下
39 |
40 | ~~~~~~~~~
41 | class User < ActiveRecord::Base
42 | # Include default devise modules. Others available are:
43 | # :token_authenticatable, :confirmable,
44 | # :lockable, :timeoutable and :omniauthable
45 |
46 | has_many :groups
47 | has_many :posts
48 |
49 | extend OmniauthCallbacks
50 |
51 | devise :database_authenticatable, :registerable,
52 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable
53 |
54 |
55 | end
56 |
57 |
58 | ~~~~~~~~~
59 |
60 |
61 | 修改 `app/models/post.rb` 加入
62 |
63 | ~~~~~~~~~
64 | belongs_to :author, :class_name => "User", :foreign_key => :user_id
65 |
66 | def editable_by?(user)
67 | user && user == author
68 | end
69 | ~~~~~~~~~
70 |
71 | 內容如下:
72 |
73 | ~~~~~~~~~
74 |
75 | class Post < ActiveRecord::Base
76 |
77 | belongs_to :group
78 | validates :content, :presence => true
79 |
80 | belongs_to :author, :class_name => "User", :foreign_key => :user_id
81 |
82 | def editable_by?(user)
83 | user && user == author
84 | end
85 |
86 | end
87 | ~~~~~~~~~
88 |
89 | {::pagebreak :/}
90 |
91 | 接著我們要把 Posts 的幾個 action 內容替換掉:
92 |
93 | ### create
94 |
95 | ~~~~~~~~
96 | def create
97 |
98 | @post = @group.posts.new(post_params)
99 | @post.author = current_user
100 |
101 | if @post.save
102 | redirect_to group_path(@group)
103 | else
104 | render :new
105 | end
106 | end
107 | ~~~~~~~~
108 |
109 | ### edit
110 |
111 | ~~~~~~~~
112 | def edit
113 | @post = current_user.posts.find(params[:id])
114 | end
115 | ~~~~~~~~
116 |
117 |
118 | ### update
119 |
120 | ~~~~~~~~
121 | def update
122 |
123 | @post = current_user.posts.find(params[:id])
124 |
125 | if @post.update(post_params)
126 | redirect_to group_path(@group)
127 | else
128 | render :edit
129 | end
130 | end
131 | ~~~~~~~~
132 |
133 | {::pagebreak :/}
134 |
135 |
136 | ### destroy
137 |
138 | ~~~~~~~~
139 | def destroy
140 |
141 | @post = current_user.posts.find(params[:id])
142 |
143 | @post.destroy
144 |
145 | redirect_to group_path(@group)
146 | end
147 | ~~~~~~~~
148 |
149 | 把 app/views/groups/show.html.erb 的內容換掉
150 |
151 | ~~~~~~~~
152 |
153 |
154 |
155 |
156 |
157 | <% if current_user && @group.editable_by?(current_user) %>
158 | <%= link_to("Edit", edit_group_path(@group) , :class => "btn btn-mini ")%>
159 | <% end %>
160 |
161 | <%= link_to("New Post", new_group_post_path(@group) , :class => "btn btn-mini btn-primary") if current_user )%>
162 |
163 |
164 |
165 |
<%= @group.title %>
166 |
167 |
<%= @group.description %>
168 |
169 |
170 |
171 |
172 |
173 | <% @posts.each do |post| %>
174 |
175 |
176 |
177 | Author : <%= post.author.name %>
178 |
179 | <%= post.content %>
180 |
181 | |
182 |
183 | <% if current_user && post.editable_by?(current_user) %>
184 | <%= link_to("Edit", edit_group_post_path(post.group, post), :class => "btn btn-mini")%>
185 | <%= link_to("Delete", group_post_path(post.group, post), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %> |
186 |
187 | <% end %>
188 | <% end %>
189 |
190 |
191 |
192 |
193 | ~~~~~~~~
194 |
--------------------------------------------------------------------------------
/manuscript/chapter-03.txt:
--------------------------------------------------------------------------------
1 | # 練習作業 3 - 為 Group 與 Post 加入使用者機制
2 |
3 | 在上一章我們完成了在 Group 裡面發表文章的功能。但是通常一個討論區的機制,必須是先加入會員才能進行相關動作。所以我們必須為討論區加入使用者機制:
4 |
5 | 使用者必須能夠 註冊 / 登入,登入後才可以開設 Group,與發表 Post,不然只能瀏覽。只有自己的 Group,Post 才能進行修改與刪除。
6 |
7 | ### 本章練習主題
8 |
9 | * 安裝 gem
10 | * 設定 devise
11 | * 撰寫全域的 method `login_required`
12 | * 利用 before_action 結合 login_required 加入登入判斷
13 | * session 的使用:current_user
14 |
15 | ### 本章參考資料
16 |
17 | * [devise](https://github.com/plataformatec/devise/wiki)
18 |
19 |
20 |
--------------------------------------------------------------------------------
/manuscript/chapter-04-1-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 4.1.2 model method 與 after create
4 |
5 | 實作以下 method 在 User model 內
6 |
7 | ~~~~~~~~~~
8 |
9 | def join!(group)
10 | participated_groups << group
11 | end
12 |
13 | def quit!(group)
14 | participated_groups.delete(group)
15 | end
16 |
17 | def is_member_of?(group)
18 | participated_groups.include?(group)
19 | end
20 |
21 | ~~~~~~~~~~
22 |
23 | 這樣我們之後,就可以在 controller ,使用 current_user.join!(group) 這樣的語法來加入 group。
24 |
25 |
26 | ### 產生 Group 後自動加入 group 作為 group 的一員
27 |
28 | 在一般的認知中,Group 的開創者應該就要是 group 的一員。這有兩種作法,一種是到 `app/controller/groups_controller.rb` 裡的 `create` action 裡面加入 `current_user.join!(@group)`
29 |
30 | ~~~~~~~~~~
31 | def create
32 | @group = current_user.groups.build(group_params)
33 | if @group.save
34 | current_user.join!(@group)
35 | redirect_to groups_path
36 | else
37 | render :new
38 | end
39 | end
40 | ~~~~~~~~~~
41 |
42 | 或者是你也可以使用 ActiveRecord 的 after_create,更漂亮的實作:
43 |
44 | ~~~~~~~~~~
45 |
46 | class Group < ActiveRecord::Base
47 |
48 | belongs_to :owner, :class_name => "User", :foreign_key => :user_id
49 | has_many :posts
50 |
51 | has_many :group_users
52 | has_many :members, :through => :group_users, :source => :user
53 |
54 |
55 | validates :title, :presence => true
56 |
57 | after_create :join_owner_to_group
58 |
59 | def editable_by?(user)
60 | user && user == owner
61 | end
62 |
63 | def join_owner_to_group
64 | members << owner
65 | end
66 |
67 | end
68 |
69 |
70 | ~~~~~~~~~~
71 |
72 | ### 解說
73 |
74 | #### after_create
75 |
76 | `after_create` 是 ActiveRecord 提供的 callbacks,意旨在簡化程式碼。類似的 callbacks 還有
77 |
78 | * before_create
79 | * before_update
80 | * after_create
81 | * after_update
82 |
83 | 等等...
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/manuscript/chapter-04-1-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 |
4 | ## Ch 4.1.3 join 與 quit action
5 |
6 | 在 Groups Controller 加入以下這兩個 action
7 |
8 | ~~~~~~~~~~
9 |
10 | def join
11 | @group = Group.find(params[:id])
12 |
13 | if !current_user.is_member_of?(@group)
14 | current_user.join!(@group)
15 | else
16 | flash[:warning] = "You already joined this group."
17 | end
18 | redirect_to group_path(@group)
19 | end
20 |
21 | def quit
22 | @group = Group.find(params[:id])
23 |
24 | if current_user.is_member_of?(@group)
25 | current_user.quit!(@group)
26 | else
27 | flash[:warning] = "You are not member of this group."
28 | end
29 |
30 | redirect_to group_path(@group)
31 |
32 | end
33 | ~~~~~~~~~~
34 |
35 | 然後在 config/routes.rb 修改 `resources :groups` 這一段,變成:
36 |
37 | ~~~~~~~~~~
38 | resources :groups do
39 | member do
40 | post :join
41 | post :quit
42 | end
43 | resources :posts
44 | end
45 | ~~~~~~~~~~
46 |
47 | 再修改 `app/views/groups/show.html.erb` 加入這一段:
48 |
49 | ~~~~~~~~~~
50 | <% if current_user %>
51 |
52 | <% if current_user.is_member_of?(@group) %>
53 | <%= link_to("Quit Group", quit_group_path(@group), :method => :post, :class => "btn btn-mini") %>
54 | <% else %>
55 | <%= link_to("Join Group", join_group_path(@group), :method => :post, :class => "btn btn-mini") %>
56 | <% end %>
57 |
58 | <% end %>
59 | ~~~~~~~~~~
60 |
61 |
62 | 修改 `app/views/groups/show.html.erb` 原先的
63 |
64 | ~~~~~~~~~~
65 | <%= link_to("New Post", new_group_post_path(@group) , :class => "btn btn-mini btn-primary") if current_user %>
66 | ~~~~~~~~~~
67 |
68 | 變成
69 |
70 | ~~~~~~~~~~
71 | <%= link_to("New Post", new_group_post_path(@group) , :class => "btn btn-mini btn-primary") if current_user.is_member_of?(@group) %>
72 |
73 | ~~~~~~~~~~
74 |
75 | ### 在 controller 裡加入 member_required
76 |
77 | 在 Post 這個 controller 裡面加入另一個 before_action
78 |
79 | ~~~~~~~~~~
80 | before_action :member_required, :only => [:new, :create ]
81 | ~~~~~~~~~~
82 |
83 | 在 `private` 下新增 `member_required` 這個 method
84 |
85 | ~~~~~~~~~~
86 | def member_required
87 | if !current_user.is_member_of?(@group)
88 | flash[:warning] = " You are not member of this group!"
89 | redirect_to group_path(@group)
90 | end
91 | end
92 | ~~~~~~~~~~
93 |
--------------------------------------------------------------------------------
/manuscript/chapter-04-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 4.1 使用者必須要是這個社團的成員才能發表文章
4 |
5 | 在第 2 章、第 3 章我們完成了 Group 與 Post 的新增與管理。在這一章我們要挑戰一點進階的課題。
6 |
7 | ### 4.1.1 has_many :through
8 |
9 | 新增 group_user 這個 model:`rails g model group_user group_id:integer user_id:integer`
10 |
11 | 執行 migration:`rake db:migrate`
12 |
13 |
14 | ~~~~~~~~~
15 |
16 | == CreateGroupUsers: migrating ===============================================
17 | -- create_table(:group_users)
18 | -> 0.0238s
19 | == CreateGroupUsers: migrated (0.0239s) ======================================
20 |
21 | ~~~~~~~~~
22 |
23 | 在 User model 加入
24 |
25 | ~~~~~~~~~
26 | has_many :group_users
27 | has_many :participated_groups, :through => :group_users, :source => :group
28 | ~~~~~~~~~
29 |
30 | 內容變成以下
31 |
32 | ~~~~~~~~~
33 | class User < ActiveRecord::Base
34 |
35 | has_many :groups
36 | has_many :posts
37 |
38 | has_many :group_users
39 | has_many :participated_groups, :through => :group_users, :source => :group
40 |
41 | extend OmniauthCallbacks
42 |
43 | devise :database_authenticatable, :registerable,
44 | :recoverable, :rememberable, :trackable, :validatable, :omniauthable
45 |
46 |
47 | def join!(group)
48 | participated_groups << group
49 | end
50 |
51 | def quit!(group)
52 | participated_groups.delete(group)
53 | end
54 |
55 | def is_member_of?(group)
56 | participated_groups.include?(group)
57 | end
58 |
59 | end
60 |
61 | ~~~~~~~~~
62 |
63 |
64 | {::pagebreak :/}
65 |
66 |
67 | 在 `app/models/group.rb` 加入
68 |
69 | ~~~~~~~~~
70 | has_many :group_users
71 | has_many :members, :through => :group_users, :source => :user
72 | ~~~~~~~~~
73 |
74 |
75 |
76 | 內容變成以下
77 |
78 | ~~~~~~~~~
79 | class Group < ActiveRecord::Base
80 |
81 | belongs_to :owner, :class_name => "User", :foreign_key => :user_id
82 | has_many :posts
83 | has_many :group_users
84 | has_many :members, :through => :group_users, :source => :user
85 |
86 | validates :title, :presence => true
87 |
88 |
89 | def editable_by?(user)
90 | user && user == owner
91 | end
92 | end
93 |
94 | ~~~~~~~~~
95 |
96 | 修改 `app/models/group_user.rb` 成
97 |
98 | ~~~~~~~~~
99 | class GroupUser < ActiveRecord::Base
100 | belongs_to :group
101 | belongs_to :user
102 | end
103 | ~~~~~~~~~
104 |
105 |
106 | ### 解說
107 |
108 | #### Many to Many 關係
109 |
110 | ~~~~~~~~~~
111 | has_many :group_users
112 | has_many :members, :through => :group_users, :source => :user
113 | ~~~~~~~~~~
114 |
115 | Rails 常見的關係有 `has_one`,`belongs_to` 與 `has_many`。`has_many :through` 是最後一種。
116 |
117 | 多數用在多對多(表間列表)的關係上。
118 |
--------------------------------------------------------------------------------
/manuscript/chapter-04.txt:
--------------------------------------------------------------------------------
1 | # 練習作業 4 - User 可以加入、退出社團
2 |
3 | ### 作業目標
4 |
5 | * user 可以在 group 頁面加入社團 / 退出社團
6 | * user 必須要是這個社團的成員才能發表文章
7 |
8 | ### 本章練習主題
9 |
10 | * has_many_belongs_to
11 | * custom routes
12 |
13 |
14 |
15 |
16 |
17 | ~~~~~~~~~
18 |
19 |
20 | class PostsController < ApplicationController
21 |
22 | before_action :find_group
23 | before_action :login_required, :only => [:new, :create, :edit,:update,:destroy]
24 | before_action :member_required, :only => [:new, :create ]
25 |
26 |
27 | def new
28 | @post = @group.posts.build
29 | end
30 |
31 | def create
32 | @post = @group.posts.new(post_params)
33 | @post.author = current_user
34 |
35 | if @post.save
36 | redirect_to group_path(@group)
37 | else
38 | render :new
39 | end
40 | end
41 |
42 | def edit
43 | @post = current_user.posts.find(params[:id])
44 | end
45 |
46 | def update
47 | @post = current_user.posts.find(params[:id])
48 |
49 | if @post.update(post_params)
50 | redirect_to group_path(@group)
51 | else
52 | render :edit
53 | end
54 | end
55 |
56 | def destroy
57 | @post = current_user.posts.find(params[:id])
58 |
59 | @post.destroy
60 |
61 | redirect_to group_path(@group)
62 | end
63 |
64 | private
65 |
66 | def post_params
67 | params.require(:post).permit(:content)
68 | end
69 |
70 | def find_group
71 | @group = Group.find(params[:group_id])
72 | end
73 |
74 | def member_required
75 | if !current_user.is_member_of?(@group)
76 | flash[:warning] = " You are not member of this group!"
77 | redirect_to group_path(@group)
78 | end
79 | end
80 | end
81 |
82 | ~~~~~~~~~
--------------------------------------------------------------------------------
/manuscript/chapter-05-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 5.1 User 必須要在使用者後台可以看到自己參加的 Group
4 |
5 |
6 | 新增 account/groups controller:`rails g controller account/groups`
7 |
8 | ~~~~~~~~
9 | identical app/controllers/account/groups_controller.rb
10 | invoke erb
11 | exist app/views/account/groups
12 | invoke test_unit
13 | identical test/controllers/account/groups_controller_test.rb
14 | invoke helper
15 | identical app/helpers/account/groups_helper.rb
16 | invoke test_unit
17 | identical test/helpers/account/groups_helper_test.rb
18 | invoke assets
19 | invoke coffee
20 | identical app/assets/javascripts/account/groups.js.coffee
21 | invoke scss
22 | identical app/assets/stylesheets/account/groups.css.scss
23 | ~~~~~~~~
24 |
25 |
26 | 新增 `app/controllers/account/groups_controller.rb` 裡的內容
27 |
28 | ~~~~~~~~
29 | class Account::GroupsController < ApplicationController
30 | before_action :login_required
31 |
32 | def index
33 | @groups = current_user.participated_groups
34 | end
35 | end
36 | ~~~~~~~~
37 |
38 |
39 | `touch app/views/account/groups/index.html.erb` 新增裡面的內容
40 |
41 | ~~~~~~~~
42 |
43 |
44 |
My Groups
45 |
46 |
47 |
48 | # |
49 | Title |
50 | Description |
51 | Post Count |
52 | Last Update |
53 |
54 |
55 |
56 | <% @groups.each do |group| %>
57 |
58 | # |
59 | <%= link_to(group.title, group_path(group)) %> |
60 | <%= group.description %> |
61 | <%= group.posts.count %> |
62 | <%= group.updated_at %> |
63 |
64 | <% end %>
65 |
66 |
67 |
68 |
69 | ~~~~~~~~
70 |
71 | 修改 `config/routes.rb` 加入
72 |
73 | ~~~~~~~~
74 | namespace :account do
75 | resources :groups
76 | end
77 | ~~~~~~~~
78 |
79 | 修改 `app/views/common/_user_nav.html.erb` 裡的
80 |
81 | ~~~~~~~~
82 | <%= render_list :class => "dropdown-menu" do |li|
83 | li << link_to("Logout",destroy_user_session_path, :method => :delete )
84 | end %>
85 | ~~~~~~~~
86 |
87 | 變成
88 |
89 | ~~~~~~~~
90 | <%= render_list :class => "dropdown-menu" do |li|
91 | li << link_to("My Group", account_groups_path)
92 | li << link_to("Logout",destroy_user_session_path, :method => :delete )
93 | end %>
94 | ~~~~~~~~
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/manuscript/chapter-05-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 5.2 User 必須要在使用者後台可以看到自己發表的文章
4 |
5 | 新增 account/posts controller:`rails g controller account/posts`
6 |
7 | ~~~~~~~~
8 | create app/controllers/account/posts_controller.rb
9 | invoke erb
10 | create app/views/account/posts
11 | invoke test_unit
12 | create test/controllers/account/posts_controller_test.rb
13 | invoke helper
14 | create app/helpers/account/posts_helper.rb
15 | invoke test_unit
16 | create test/helpers/account/posts_helper_test.rb
17 | invoke assets
18 | invoke coffee
19 | create app/assets/javascripts/account/posts.js.coffee
20 | invoke scss
21 | create app/assets/stylesheets/account/posts.css.scss
22 | ~~~~~~~~
23 |
24 | 新增 `app/controllers/account/groups_controller.rb` 裡的內容
25 |
26 |
27 | ~~~~~~~~
28 | class Account::PostsController < ApplicationController
29 |
30 | before_action :login_required
31 |
32 | def index
33 | @posts = current_user.posts
34 | end
35 |
36 | end
37 | ~~~~~~~~
38 |
39 | {::pagebreak :/}
40 |
41 | `touch app/views/account/posts/index.html.erb` 新增裡面的內容
42 |
43 |
44 | ~~~~~~~~
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Content |
53 | Group name |
54 | Last Update |
55 |
56 |
57 |
58 |
59 | <% @posts.each do |post| %>
60 |
61 | <%= post.content %> |
62 |
63 | <%= post.group.title %> |
64 |
65 | <%= post.updated_at %> |
66 |
67 |
68 | <%= link_to("Edit", edit_group_post_path(post.group, post), :class => "btn btn-mini")%>
69 | <%= link_to("Delete", group_post_path(post.group, post), :class => "btn btn-mini", :method => :delete, :confirm => "Are you sure?" ) %> |
70 |
71 | <% end %>
72 |
73 |
74 |
75 |
76 | ~~~~~~~~
77 |
78 |
79 | 修改 `config/routes.rb` 加入 `resources :posts`
80 |
81 | ~~~~~~~~
82 | namespace :account do
83 | resources :groups
84 | resources :posts
85 | end
86 | ~~~~~~~~
87 |
88 |
89 | {::pagebreak :/}
90 |
91 | 修改 `app/views/common/_user_nav.html.erb` 裡的
92 |
93 | ~~~~~~~~
94 | <%= render_list :class => "dropdown-menu" do |li|
95 | li << link_to("My Group", account_groups_path)
96 | li << link_to("Logout",destroy_user_session_path, :method => :delete )
97 | end %>
98 | ~~~~~~~~
99 |
100 | 變成
101 |
102 | ~~~~~~~~
103 | <%= render_list :class => "dropdown-menu" do |li|
104 | li << link_to("My Group", account_groups_path)
105 | li << link_to("My Post", account_posts_path)
106 | li << link_to("Logout",destroy_user_session_path, :method => :delete )
107 | end %>
108 | ~~~~~~~~
109 |
110 |
--------------------------------------------------------------------------------
/manuscript/chapter-05-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 5.3 文章列表的排序要以發表時間 DESC 排序
4 |
5 | 你應該有注意到,在 My Post 裡的每篇文章是以文章先後發表順序排列的,也就是越晚發表的文章排越下面。但是這樣預設的排列方式違反一般開發者的使用習慣。
6 |
7 | 比較適當的排列方式應該是以文章的「修改時間」「倒序排列」。也就是最近修改的文章要排在越上面才行。
8 |
9 |
10 | 修改 `app/controllers/account/posts_controller.rb` 裡的內容
11 |
12 |
13 | ~~~~~~~~
14 | class Account::PostsController < ApplicationController
15 |
16 | before_action :login_required
17 |
18 | def index
19 | @posts = current_user.posts.order("updated_at DESC")
20 | end
21 |
22 | end
23 | ~~~~~~~~
24 |
25 | 這樣就能達到想要的效果了。
26 |
27 |
--------------------------------------------------------------------------------
/manuscript/chapter-05-4.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 5.4 Group 的排序要以文章數量的熱門度 DESC 排序
4 |
5 | 在 `app/views/account/groups/index.html.erb` 裡面你應該有發現這一段 `<%= group.posts.count %>` code。
6 |
7 | ~~~~~~~~
8 |
9 |
10 |
11 | <% @groups.each do |group| %>
12 |
13 | # |
14 | <%= link_to(group.title, group_path(group)) %> |
15 | <%= group.description %> |
16 | <%= group.posts.count %> |
17 | <%= group.updated_at %> |
18 |
19 | <% end %>
20 |
21 |
22 |
23 |
24 | ~~~~~~~~
25 |
26 |
27 | `group.posts.count` 這一段 code 是不太健康的,因為它產生了這樣的 SQL query。
28 |
29 | ~~~~~~~~
30 | (0.2ms) SELECT COUNT(*) FROM `posts` WHERE `posts`.`group_id` = 1
31 | ~~~~~~~~
32 |
33 | 如果你有開發過網頁程式 application 的經驗,就知道在迴圈裡面跑 count 對效能是相當傷的。實務上我們相當不建議這麼做。
34 |
35 | 那麼這一段程式碼要怎麼改善呢?比較直觀的想法,就是在 groups 這個 table 再開一欄叫作 `posts_count` 的欄位。然後在
36 | create action 裡面對 `posts_count` +1
37 |
38 | ~~~~~~~~
39 |
40 | def create
41 |
42 | @post = @group.posts.new(post_params)
43 | @post.author = current_user
44 |
45 | if @post.save
46 | Group.increment_counter(:posts_count, @group.id)
47 | redirect_to group_path(@group)
48 | else
49 | render :new
50 | end
51 | end
52 |
53 |
54 | ~~~~~~~~
55 |
56 | {::pagebreak :/}
57 |
58 | 在 `destroy action` 裡面對 `posts_count` - 1
59 |
60 | ~~~~~~~~
61 | def destroy
62 |
63 | @post = current_user.posts.find(params[:id])
64 | @post.destroy
65 |
66 | Group.decrement_counter(:posts_count, @group.id)
67 | redirect_to group_path(@group)
68 | end
69 |
70 | ~~~~~~~~
71 |
72 |
73 | 不過,其實倒也不用這麼麻煩。Rails 內建一個叫作 `counter_cache` 的機制,只要內建子關係的 count 欄位,如 `posts_count`。
74 |
75 | `rails g migration add_posts_count_to_group`
76 |
77 | ~~~~~~~~
78 | invoke active_record
79 | create db/migrate/20130531183331_add_posts_count_to_group.rb
80 | ~~~~~~~~
81 |
82 | 填入
83 |
84 | ~~~~~~~~
85 |
86 | class AddPostsCountToGroup < ActiveRecord::Migration
87 | def change
88 | add_column :groups, :posts_count, :integer, :default => 0
89 | end
90 | end
91 |
92 | ~~~~~~~~
93 |
94 | 執行 `rake db:migrate`
95 |
96 |
97 | ~~~~~~~~
98 |
99 | == AddPostsCountToGroup: migrating ===========================================
100 | -- add_column(:groups, :posts_count, :integer, {:default=>0})
101 | -> 0.0232s
102 | == AddPostsCountToGroup: migrated (0.0232s) ==================================
103 |
104 | ~~~~~~~~
105 |
106 | 然後在 `app/models/post.rb` 的欄位裡,這樣設定:
107 |
108 | ~~~~~~~~
109 | belongs_to :group, :counter_cache => true
110 | ~~~~~~~~
111 |
112 | 以後只要 post 遇到 create 或者是 destroy,就會自動對這個欄位 +1 / -1 。不需要在 controller 裡面另外動作。
113 |
114 |
115 | {::pagebreak :/}
116 |
117 | 而上了 counter_cache 之後,`<%= group.posts.size %>` 以後執行時,也會優先去找 `posts_count` 裡的值,而不是真的下 MySQL 的 COUNT 去實際算值。
118 |
119 | 而加了這個 posts_count 欄位之後,我們就可以修改 index 裡的排序規則變成以文章數量的熱門度 DESC 排序
120 |
121 |
122 | ~~~~~~~~
123 | def index
124 | @groups = current_user.participated_groups.order("posts_count DESC")
125 | end
126 | ~~~~~~~~
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/manuscript/chapter-05.txt:
--------------------------------------------------------------------------------
1 | # 練習作業 5 - 實作簡單的 Account 後台機制
2 |
3 | ### 作業目標
4 |
5 | * user 可以在 account 頁面找到自己曾經加入的社團
6 | * user 可以至 account 找到自己曾經發表過的文章
7 | * 每個 Group 要秀出現在有多少 post 數量
8 | * 文章列表的排序要以發表時間以 DESC 排序。
9 |
10 | ### 練習主題
11 |
12 | * 使用 [counter_cache](http://railscasts.com/episodes/23-counter-cache-column) 取代直接對資料庫的 count
13 | * 了解 ORM 的基本用法
14 |
15 |
16 |
--------------------------------------------------------------------------------
/manuscript/chapter-06-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 6.1 使用系統 helper 整理 code
4 |
5 | 我們已經初步完成了這個系統。但有一些設計看起來還是令人不太滿意的。比如說
6 |
7 |
8 | `post.updated_at` 與 `group.updated_at`。
9 |
10 | 顯示出來的會是 `2013-05-31 14:47:31 UTC` 這種格式。
11 |
12 |
13 | ### date.to_s
14 |
15 | 但其實我們想要顯示的好看一點,可以改成這樣的寫法:
16 |
17 |
18 | * `post.updated_at.to_s(:long)` => May 31, 2013 18:04
19 | * `group.updated_at.to_s(:short)` => 31 May 18:04
20 |
21 |
22 | ### simple_format
23 |
24 | 當我們在顯示 `post.content` 時,有個困擾。我們在輸入框輸入
25 |
26 | ~~~~~
27 | a
28 | b
29 | c
30 | ~~~~~
31 |
32 | 但在系統輸出的時候,HTML 並不會自動斷行,會顯示成
33 |
34 | ~~~~~
35 | a b c
36 | ~~~~~
37 |
38 | 用 Enter 斷行的 `\r` 或 `\n` 在 HTML 裡面並不起作用。若要在 HTML 裡面正確斷行必須要使用 `
`。
39 |
40 | 若在以往,要漂亮顯示文本,開發者必須自行寫一段 nl2br 的轉換 helper。不過 Rails 裡面內建 `simple_format` 這個 helper,可以自動幫我們處理這種雜事。
41 |
42 | `<%= simple_format(post.content) %>`
43 |
44 |
45 | ### truncate
46 |
47 |
48 | 在顯示 group.title 時,有時候我們也會遇到標題太長的問題,導致破版。所以要砍字,再加上 "..."
49 |
50 | Rails 內建了一個 `truncate` helper 可以快速辦到這個效果
51 |
52 | `<%= truncate(@group.title, :length => 17 ) %>`
53 |
--------------------------------------------------------------------------------
/manuscript/chapter-06-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 6.2 自己撰寫的 helper 包裝 html
4 |
5 | Helper 是一些使用在 Rails 的 View 當中,用 Ruby 產生/整理 HTML code 的一些小方法。通常被放在 `app/helpers` 下。預設的 Helper 名字是對應 Controller 的,產生一個 Controller 時,通常會產生一個同名的 Helper。如 `PostsController` 與 `PostsHelper`。
6 |
7 | ### 使用情境
8 |
9 | 使用 Helper 的情境多半是:
10 |
11 | * 產生的 HTML code 需要與原始程式碼進行一些邏輯混合,但不希望 View 裡面搞得太髒。
12 | * 需要與預設的 Rails 內建的一些方便 Helper 交叉使用。
13 |
14 | 使用 Helper 封裝程式碼可以帶給專案以下一些優點:
15 |
16 | * Don't repeat yourself(DRY)程式碼不重複
17 | * Good Encapsulation好的封裝性
18 | * 提供 view 模板良好的組織
19 | * 易於修改程式碼
20 |
21 | ### 範例
22 |
23 | 在剛剛的專案當中,顯示 Post 的程式碼如下:
24 |
25 | ~~~~~~~~~
26 | <%= @post.content %> %>
27 | ~~~~~~~~~
28 |
29 | 隨著專案變遷,這樣的程式碼,可能會依需求改成:(需要內容斷行)
30 |
31 | ~~~~~~~~~
32 | <%= simple_format(@post.content) %> %>
33 | ~~~~~~~~~
34 |
35 | 之後又改成 (只顯示頭一百字)
36 |
37 | ~~~~~~~~~
38 | <%= truncate(simple_format(@post.content), :lenth => 100) %>
39 | ~~~~~~~~~
40 |
41 |
42 | 而麻煩的是,這樣類似的內容,常常在專案出現。每當需求變更,開發者就需要去找出來,有十個地方,就需要改十遍,很是麻煩。
43 |
44 | {::pagebreak :/}
45 |
46 | Helper 就是用在這樣的地方。與其一開始寫下
47 |
48 | ~~~~~~~~~
49 | <%= @post.content %> %>
50 | ~~~~~~~~~
51 |
52 | 不如,一開始就設計一個 Helper ` <%= render_post_content(@post) %>`
53 |
54 | ~~~~~~~~~
55 | def render_post_content(post)
56 | truncate(simple_format(post.content), :lenth => 100)
57 | end
58 | ~~~~~~~~~
59 |
60 | 以後變更需求就只要修改一個地方即可。
61 |
62 | ### 更多的 Partial 用法
63 |
64 |
65 |
--------------------------------------------------------------------------------
/manuscript/chapter-06-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 6.3 使用 partial 整理 html
4 |
5 | Partial 也是 Rails 提供用來整理 View 的一種方式,開發者可以使用 Partial 技巧將太長的 View 整理切該成好維護的小片程式碼。
6 |
7 |
8 | Helper 是一些使用在 Rails 的 View 當中,用 Ruby 產生/整理 HTML code 的一些小方法。通常被放在 `app/helpers` 下。預設的 Helper 名字是對應 Controller 的,產生一個 Controller 時,通常會產生一個同名的 Helper。如 `PostsController` 與 `PostsHelper`。
9 |
10 | ### 使用情境
11 |
12 | 什麼時候應該將把程式碼搬到 Partial 呢?
13 |
14 | *long template | 如果當檔 HTML 超過兩頁
15 | *highly duplicated | HTML 內容高度重複
16 | *independent blocks | 可獨立作為功能區塊
17 |
18 | ### 常見範例
19 |
20 | * nav/user_info
21 | * nav/admin_menu
22 | * vendor_js/google_analytics
23 | * vendor_js/disqus_js
24 | * global/footer
25 |
26 |
27 |
28 | {::pagebreak :/}
29 |
30 | 本書範例 project 裡面的 layout/application.html.erb 就是很好的範例。
31 |
32 | ~~~~~~~~~
33 | <%= render :partial => "common/menu" %>
34 |
35 |
36 |
37 | <%= notice_message %>
38 |
39 |
40 |
41 | <%= yield %>
42 | <%= yield (:sidebar) %>
43 |
44 |
45 |
46 |
47 | <%= render :partial => "common/footer" %>
48 |
49 |
50 |
51 |
52 | <%= render :partial => "common/bootstrap_modal" %>
53 | <%= render :partial => "common/facebook_js" %>
54 | <%= render :partial => "common/google_analytics" %>
55 |
56 | <%= javascript_include_tag "application" %>
57 |
58 | <%= yield :javascripts %>
59 |
60 | ~~~~~~~~~
61 |
62 | ### 更多的 Partial 用法
63 |
64 |
--------------------------------------------------------------------------------
/manuscript/chapter-06-4.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 6.4 使用 scope 整理 query
4 |
5 | 在 Ch 5.3 裡面,我們在 index controller 裡面設計了一行程式碼,讓文章永遠按照最新的時間降序排列。
6 |
7 |
8 | ~~~~~~~~
9 | class Account::PostsController < ApplicationController
10 |
11 | def index
12 | @posts = current_user.posts.order("updated_at DESC")
13 | end
14 |
15 | end
16 | ~~~~~~~~
17 |
18 | `posts.order("updated_at DESC")` 其實是一段可能會很常用到的程式碼。如果 View 需要 Helper 包裝整理,其實我們也需要工具來整理 Query。
19 |
20 | 這段程式碼比較理想的方式其實是:
21 |
22 | ~~~~~~~~
23 | class Account::PostsController < ApplicationController
24 |
25 | def index
26 | @posts = current_user.posts.recent
27 | end
28 |
29 | end
30 | ~~~~~~~~
31 |
32 | 讓維護者可以從程式碼裡面知道這一個 controller 提供的是「最近的文章」。Rails 在 ORM 中提供了 Scope,可以讓開發者包裝 query:
33 |
34 | ~~~~~~~~
35 | class Post < ActiveRecord::Base
36 | scope :recent, -> { order("updated_at DESC") }
37 | end
38 | ~~~~~~~~
39 |
40 | {::pagebreak :/}
41 |
42 | ### Scope 串接
43 |
44 | 甚至也提供串接的功能:
45 |
46 | ~~~~~~~~
47 | class Post < ActiveRecord::Base
48 | scope :recent, -> { order("updated_at DESC") }
49 | scope :published, -> { where(:published => true) }
50 | end
51 | ~~~~~~~~
52 |
53 | 你可以用不同的條件用「語意」串接組起想要撈出的資料。
54 |
55 | ~~~~~~~~
56 | class Account::PostsController < ApplicationController
57 |
58 | before_action :login_required
59 |
60 | def index
61 | @posts = current_user.posts.published.hot
62 | end
63 |
64 | end
65 | ~~~~~~~~
66 |
67 |
68 | ### 更多的 scope 用法
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/manuscript/chapter-06.txt:
--------------------------------------------------------------------------------
1 | # 練習作業 6 - Refactor code
2 |
3 |
4 | ### 作業目標
5 |
6 | * 使用系統 helper / 自己撰寫的 helper 包裝 html
7 | * 使用 partial 整理 html
8 | * 使用 scope 整理 query
9 |
10 |
11 | ### 練習主題
12 |
13 | * 練習使用內建 helper
14 | * 練習拆 partial
15 | * 練習使用 scope 機制包裝不同 condition 的 SQL query
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/manuscript/chapter-07-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 7.1 Rake
4 |
5 | Rake 的意思就是字面上直觀的 Ruby Make,Ruby 版 Make:用 Ruby 開發的 Build Tool。
6 |
7 | 你也許會問,Ruby 是直譯語言,不需要 compile,為何還需要 Build Tool。
8 |
9 | 與其說 Rake 是一套 Build Tool,還不如說是一套 task 管理工具。我們通常會使用 Ruby based 的 Rake 在 Rails 專案裡編寫 task。
10 |
11 | ### Rails 內的 rake tasks
12 |
13 | `rake -T` 會秀出一大串這個 Rails project 可用的 rake tasks (包含 plugin 內建的 task 也會秀出來)。
14 |
15 | ~~~~~~~~
16 | rake about # List versions of all Rails frameworks and the environment
17 | rake assets:clean # Remove compiled assets
18 | rake assets:precompile # Compile all the assets named in config.assets.precompile
19 | rake db:create # Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)
20 | rake db:drop # Drops the database for the current Rails.env (use db:drop:all to drop all databases)
21 | .....
22 |
23 | ~~~~~~~~
24 |
25 |
26 | ### 清空系統重跑 migration 與種子資料的 rake dev:build
27 |
28 |
29 | 在還未上線的開發階段,我們有時會不是那麼喜歡被 db migration 拘束,有時候下錯了 migration,或想直接清空系統重來,實在是件麻煩事。因此寫支 rake 檔自動化是個良招。
30 |
31 | 新增 `lib/tasks/dev.rake`
32 |
33 | ~~~~~~~~
34 | namespace :dev do
35 | desc "Rebuild system"
36 | task :build => ["tmp:clear", "log:clear", "db:drop", "db:create", "db:migrate"] task :rebuild => [ "dev:build", "db:seed" ]
37 | end
38 | ~~~~~~~~
39 |
40 | 執行 rake dev:build 看看會發生什麼事吧:
41 |
42 | ~~~~~~~~
43 | $ rake dev:build
44 | ~~~~~~~~
45 |
46 |
47 | `rake dev:build` 是個無敵大絕:清空系統重來!
48 |
49 | 不過系統清空重來還是很麻煩,我們還是需要一些種子資料,如種子看板,種子文章,管理者帳號,測試用一般帳號。
50 |
51 |
52 | ### rake db:seed
53 |
54 | `db/seed.rb` 就是讓你撰寫這些種子資料,可以在專案開始時自動產生一些種子資料。
55 |
56 |
57 | I> ## 注意
58 | I>
59 | I> 請注意!seed.rb 裡放的是種子資料!不是假資料!假資料應該要放在 rake 檔裡,而不是放在種子檔裡!
60 | I>
61 |
62 | ~~~~~~~~
63 |
64 | admin = User.new(:email => "admin@example.org", :password => "123456",
65 | :password_confirmation => "123456")
66 | admin.is_admin = true
67 | admin.save!
68 |
69 | normal_user = User.new(:email => "user1@example.org", :password => "123456",
70 | :password_confirmation => "123456")
71 | normal_user.save!
72 |
73 | board = Board.create!(:name => "System Announcement")
74 |
75 | post = board.posts.build(:title => "First Post", :content => "This is a demo post")
76 | post.user_id = admin.id
77 | post.save!
78 |
79 | ~~~~~~~~
80 |
81 |
82 |
83 |
84 |
85 | X>## 練習作業
86 | X>
87 | X> * 撰寫一個 task 可以自動連續執行 rake db:drop ; rake db:create ; rake db:migrate ; rake db:seed
88 | X> * 撰寫一個 task 可以執行 rake dev:fake 生假資料 ( 自己寫 namespace : dev, 裡面放一個 task 叫做 fake,fake 資料用 Populator 生) # 請自行練習
89 |
90 |
--------------------------------------------------------------------------------
/manuscript/chapter-07.txt:
--------------------------------------------------------------------------------
1 | # 練習作業 7 - 撰寫自動化 Rake 以及 db:seed
2 |
3 | ### 作業目標
4 |
5 | 用 Rake 撰寫自動化步驟,生假資料。
6 |
7 | 寫一個 rake 可以達成以下步驟:「砍 db => 建 db => 跑 migration => 生種子資料」,另一個 rake 是生假 Group 與假文章。
8 |
9 | ### 練習主題
10 |
11 | * 操作 rake -T
12 | * 撰寫一個 task 可以自動連續執行 rake db:drop ; rake db:create ; rake db:migrate ; rake db:seed
13 | * 撰寫一個 task 可以執行 rake dev:fake 生假資料 ( 自己寫 namespace : dev, 裡面放一個 task 叫做 fake,fake 資料用 Populator 生) # 請自行練習
14 |
15 |
16 | ### 參考資料
17 |
18 | * [Ruby on Rails Rake Tutorial](http://jasonseifer.com/2010/04/06/rake-tutorial)
19 | * [What’s New in Edge Rails: Database Seeding](http://ryandaigle.com/articles/2009/5/13/what-s-new-in-edge-rails-database-seeding)
20 |
21 |
--------------------------------------------------------------------------------
/manuscript/chapter-08-1.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 8.1 佈署 Rails Production 所需要的環境
4 |
5 | 佈署 Rails Application 是件說簡單很簡單,說複雜也很複雜的事。佈署的手法有很多種搭配,筆者最推薦的其實是在 Ubuntu / Debian 安裝 Ruby Enterprise Edition,web sever 使用 nginx + mod_rails 的組合。之後再撰寫 Capistrano 的 recipe 來 deploy。
6 |
7 |
8 | ### 為什麼要用獨立的 Ruby 版本,而不使用系統 Ruby?
9 |
10 | 因為系統 Ruby 通常綑綁了背後的套件系統(RubyGem),Rails 是個腳步前進很快的生態圈。而各樣相依套件有時候也會限定 RubyGem 的版本。很多時候,在開發或佈署上就會踩到大地雷。
11 |
12 | 所以會建議在系統上跑獨立的 Ruby。
13 |
14 | ### 為什麼要用 Ubuntu / Debian ,而不是使用 CentOS?
15 |
16 | 還是跟 Rails 是個腳步前進很快的生態圈有相當大的關係。Gem 的前進腳步很快,很多時候只 compatible 新的 library,而 CentOS 上很多 package 都已經 outdate 了。實際佈署上會踩到很多雷。而 Ubuntu / Debian 上的 package 更新速度非常快。所以也是首選。
17 |
18 |
19 | ### 有沒有 Best Practice 懶人包?
20 |
21 | 有。其實佈署真的不算是件易事,在佈署中最容易踩到的雷當數 ImageMagick ( rmagick gem) 與 MySQL ( mysql2 gem)。偏偏這幾乎是每個網站最常會用到的兩個 gem。而且裝爛了很難重裝。
22 |
23 | 這是我們公司標準用來裝機的 Step by Step guide: 基本上已經排除不少裝機時可能會遇到的狀況。
24 |
25 |
26 | ### 哪裡買域名和租 VPS?
27 |
28 |
29 | 我是在 enom.com 買域名,管理介面還算蠻好用的。
30 |
31 | 至於 VPS 是去 [Linode](http://linode.com/) 租的。算便宜大碗,速度上也能接受。若純練習也可到 [AWS EC2](http://aws.amazon.com/ec2/) 租東京的 micro instance。
32 |
--------------------------------------------------------------------------------
/manuscript/chapter-08-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Ch 8.2 Capistrano
4 |
5 | [Capistrano](https://github.com/capistrano/capistrano/wiki) 是 37 signals 開發的一套 automate deploy tool。也是許多 Rails Developer 推薦的一套佈署工具。
6 |
7 |
8 | 也許你要問,為什麼要用工具 deploy 專案呢?那是因為佈署 application 是由數道繁瑣的手續構成。首先,Rails 佈署程式並不像 php 那樣簡單,上傳檔案成功就完事了。要佈署一個 Rails Application,你必須連到遠端 server,然後 checkout 程式碼,跑一些 migration,重開 server 生效,重開 memcached,重開 search daemon ....
9 |
10 | 這些連續動作有時候往往一閃神即是災難。
11 |
12 | 而手動 deploy 在只有一個人一台機器時還勉強說得過去。當開發人員一多或機器一多,馬上就會要了大家的命。
13 |
14 | Capistrano 固然強大,但官方網站的文件卻讓人不易讀懂。 Bootstrappets 內建了一個已經寫好的 recipe,可以直接使用。
15 |
16 | (放在 config/deploy.rb )
17 |
18 | {::pagebreak :/}
19 |
20 | ~~~~~~~~
21 | # -*- encoding : utf-8 -*-
22 |
23 | raw_config = File.read("config/config.yml")
24 | APP_CONFIG = YAML.load(raw_config)
25 |
26 | require "./config/boot"
27 | require "bundler/capistrano"
28 | require "rvm-capistrano"
29 |
30 | default_environment["PATH"] = "/opt/ruby/bin:/usr/local/bin:/usr/bin:/bin"
31 |
32 | set :application, "groupme"
33 | set :repository, "git@github.com:example/#{application}.git"
34 | set :deploy_to, "/home/apps/#{application}"
35 |
36 | set :branch, "master"
37 | set :scm, :git
38 |
39 | set :user, "apps"
40 | set :group, "apps"
41 |
42 | set :deploy_to, "/home/apps/#{application}"
43 | set :runner, "apps"
44 | set :deploy_via, :remote_cache
45 | set :git_shallow_clone, 1
46 | set :use_sudo, false
47 | set :rvm_ruby_string, '1.9.3'
48 |
49 | set :hipchat_token, APP_CONFIG["production"]["hipchat_token"]
50 | set :hipchat_room_name, APP_CONFIG["production"]["hipchat_room_name"]
51 | set :hipchat_announce, false # notify users?
52 |
53 | role :web, "groupme.com" # Your HTTP server, Apache/etc
54 | role :app, "groupme.com" # This may be the same as your `Web` server
55 | role :db, "groupme.com" , :primary => true # This is where Rails migrations will run
56 |
57 | set :deploy_env, "production"
58 | set :rails_env, "production"
59 | set :scm_verbose, true
60 | set :use_sudo, false
61 |
62 |
63 | namespace :deploy do
64 |
65 | desc "Restart passenger process"
66 | task :restart, :roles => [:web], :except => { :no_release => true } do
67 | run "touch #{current_path}/tmp/restart.txt"
68 | end
69 | end
70 |
71 |
72 | namespace :my_tasks do
73 | task :symlink, :roles => [:web] do
74 | run "mkdir -p #{deploy_to}/shared/log"
75 | run "mkdir -p #{deploy_to}/shared/pids"
76 |
77 | symlink_hash = {
78 | "#{shared_path}/config/database.yml" => "#{release_path}/config/database.yml",
79 | "#{shared_path}/config/s3.yml" => "#{release_path}/config/s3.yml",
80 | "#{shared_path}/uploads" => "#{release_path}/public/uploads",
81 | }
82 |
83 | symlink_hash.each do |source, target|
84 | run "ln -sf #{source} #{target}"
85 | end
86 | end
87 |
88 | end
89 |
90 | namespace :remote_rake do
91 | desc "Run a task on remote servers, ex: cap staging rake:invoke task=cache:clear"
92 | task :invoke do
93 | run "cd #{deploy_to}/current; RAILS_ENV=#{rails_env} bundle exec rake #{ENV['task']}"
94 | end
95 | end
96 |
97 | after "deploy:finalize_update", "my_tasks:symlink"
98 |
99 | ~~~~~~~~
100 |
--------------------------------------------------------------------------------
/manuscript/chapter-08-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 |
4 | ## Ch 8.3 Capistrano 常用指令
5 |
6 | ### cap deploy:setup
7 |
8 | 第一次使用,運行此行指令,Capistrano 就會遠端到機器上幫你把 Capistrano 所需的一些目錄和檔案先預備好
9 |
10 | ### cap deploy
11 |
12 | Deploy 專案到遠端
13 |
14 | ### cap deploy:migrate
15 |
16 | 遠端執行 migration
17 |
18 | ### cap deploy:rollback
19 |
20 | deploy 的這一版本爛了,想回到沒爛的上一版本。
21 |
22 | ### cap deploy:restart
23 |
24 | 純粹重開 application
25 |
26 | {::pagebreak :/}
27 |
28 | ## Ch 8.4 Deploy with Rails 4
29 |
30 | Rails 4 在部署上 Server 時有時候會遇到 Asset Compile 不過的問題。
31 |
32 | ### Rails 3
33 |
34 |
35 | `config/production.rb`
36 |
37 | ~~~~~~~~
38 | config.serve_static_assets = false
39 | config.assets.compile = false
40 | ~~~~~~~~
41 |
42 |
43 | ### Rails 4
44 |
45 | 在 Rails 4 上要修改成
46 |
47 | ~~~~~~~~
48 | config.serve_static_assets = true
49 | config.assets.compile = true
50 | config.assets.compress = true
51 | config.assets.configure do |env|
52 | env.logger = Rails.logger
53 | end
54 | ~~~~~~~~
--------------------------------------------------------------------------------
/manuscript/chapter-08.txt:
--------------------------------------------------------------------------------
1 | # 練習作業 8 - 將專案 deploy 到租來的 VPS
2 |
3 |
4 | ### 作業目標
5 |
6 | 在租來的 VPS 上面建置 Ruby on Rails production 環境,使用 Ruby Enterprise Edition 與 mod_rails。使用 [Capistrano](https://github.com/capistrano/capistrano/wiki) 佈署 application。
7 |
8 | ### 練習主題
9 |
10 | * 學會如何自動部署專案
11 | * 使用 capistrano 自動部署專案
12 | * 操作 cap deploy:setup
13 | * 操作 cap deploy
14 | * 操作 cap deploy:rollback
15 | * 操作 cap deploy:restart
16 |
17 |
18 | ### 參考資料
19 |
20 | * [rails-nginx-passenger-ubuntu](https://github.com/jnstq/rails-nginx-passenger-ubuntu)
21 | * [AWDR4](http://pragprog.com/titles/rails4/agile-web-development-with-rails) deploy 章節
22 |
--------------------------------------------------------------------------------
/manuscript/chapter-09-1.txt:
--------------------------------------------------------------------------------
1 |
2 | ## SCSS
3 |
4 | SCSS 是一套新型的 Asset 語言,可以用「更合理的方式」撰寫 CSS。舉例來說:
5 |
6 | ~~~~~~~~
7 | .content{ margin: 2em 0;}
8 | .content h1{ font-size: 2em;}
9 | ~~~~~~~~
10 |
11 | 在 SCSS 裡可以寫成巢狀的
12 |
13 | ~~~~~~~~
14 | .content{
15 | margin: 2em 0;
16 | h1{font-size: 2em;}
17 | }
18 | ~~~~~~~~
19 |
20 |
21 | 編譯器會自動幫你編譯成 CSS。如此一來好維護多了。
22 |
23 |
24 | {::pagebreak :/}
25 |
26 | 此外,SCSS 還支援一些強大的功能:如變數、函數、數學、繼承、mixin …等等。
27 |
28 | 這些內建功能,有多方便呢?就拿變色來說吧。在進行網頁 protyping 時,更改全站配色或者是直接提供兩個以上的設計,對設計師來說是家常便飯的事。
29 |
30 | 但更改全站配色卻是相當麻煩的一件事,因為「尋找 + 全數取代」,並不能保證最後會有正確的結果。很有可能:你更改了所有 CSS 中涉及連結的顏色,卻發現在全數取代的過程中,不小心也改到邊框的顏色。
31 |
32 | 但是 SCSS 可以讓我們使用變數去指定特定 style 的顏色。相當厲害。
33 |
34 | ~~~~~~~
35 | $border-color: #3bbfce;
36 | $link-color: #3bbfcf'
37 | .content{
38 | border-color: $border-color;
39 | a{ color: $link-color; }
40 | }
41 | ~~~~~~~
42 |
43 | 會產生出
44 |
45 |
46 | ~~~~~~~
47 | .content{ border-color: #3bbfce; }
48 | .content a{color: #3bbfcf; }
49 | ~~~~~~~
50 |
51 | ### Compass
52 |
53 | 而 SCSS 上的 [Compass](http://compass-style.org/) 這套 Framework 更是厲害,它提供了不少 mixin 可以讓 Developer 更加省事。
54 |
55 | 就如我們常常使用的圓角框技巧來說好了,以往我們要設計一個圓角框,必須囉哩八嗦的這樣寫:
56 |
57 | ~~~~~~~
58 | #border-radius {
59 | -moz-border-radius: 25px;
60 | -webkit-border-radius: 25px;
61 | -o-border-radius: 25px;
62 | -ms-border-radius: 25px;
63 | -khtml-border-radius: 25px;
64 | border-radius: 25px;
65 | }
66 | ~~~~~~~
67 |
68 | 而使用 Compass 我們只要這樣寫就可以了:
69 |
70 | ~~~~~~~
71 | #border-radius { @include border-radius(25px); }
72 | ~~~~~~~
73 |
74 |
75 |
--------------------------------------------------------------------------------
/manuscript/chapter-09-2.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## CoffeeScript
4 |
5 | Asset Pipeline 也支援了 [CoffeeScript](http://jashkenas.github.com/coffee-script/)。CoffeeScript 本身也是一種程式語言,開發者可以透過撰寫 CoffeeScript,編譯產生 JavaScript。它的語法有點像是 Ruby 與 Python 的混合體。
6 |
7 | Plurk 前創辦人 amix 曾寫過一篇這樣的 post:[CoffeeScript: The beautiful way to write Javascript](http://amix.dk/blog/post/19612) 來這樣形容 CoffeeScript:「以更漂亮的方式撰寫 JavaScript」。
8 |
9 | 他認為目前 JavaScript 存在幾種問題:
10 |
11 | * JavaScript 是 functional language
12 | * 雖然是 OOP,但卻是 prototype-based 的 JavaScript 是 dynamic language,更像 Lisp 而不是 C/Java,但卻用了 C/Java 的語法。
13 | * 名字裡面有 Java,但卻和 Java 沒什麼關係。
14 | * 明明是 functional & dynamic laungaue,更偏向 Ruby / Python,卻使用了 C / Java 的 syntax,原本可以是一門很美的語言,卻活生生的變成了悲劇。
15 |
16 | 而 CoffeeScript 的誕生,原因就是就是為了扭正這樣的局面,重新讓寫 JavaScript 這件事也可以變得「很美」。
17 |
18 | ### CoffeeScript : The Good Part
19 |
20 | CoffeeScript 留下了 JavaScript 的 Good Parts,而在設計上極力消除 JavaScript 原生特性會產生的缺點,例如:
21 |
22 | #### 消除到處污染的全域變數
23 |
24 | 開發者在寫 JavaScript 時,常不自覺的使用全域變數,導致很多污染問題。而透過 CoffeeScript 生出來的 JavaScript,變數一律為區域變數( 以 var 開頭)
25 |
26 | #### Protected code
27 |
28 | 使用 CoffeeScript 撰寫 function,產生出來的 JavaScript 必以一個 anonymous function: function(){}(); 自我包裹,獨立運作不干擾到其他 function。
29 |
30 | #### 使用 -> 和 indent(縮排) 讓撰寫 function 更不容易出錯
31 |
32 | 在撰寫 JavaScript 時,最令人不爽的莫過於 `function(){}();`,這些複雜的括號和分號稍微一漏,程式就不知道死在哪裡了….
33 |
34 | CoffeeScript 自動產生出來的 JavaScript 能夠確保括號們絕對不會被漏掉。
35 |
36 | #### 更容易偵測 syntax error 並攔阻
37 |
38 | JavaScript 在 syntax error 時,非常難以偵測錯誤,幾乎是每個程式設計師的夢靨。而 CoffeeScript 是一門需要 compile 的語言,可以藉由這樣的特性擋掉 syntax error 的機會。
39 |
40 | #### 實作物件導向更簡單
41 |
42 | Javascript 是一種物件導向語言,裡面所有東西幾乎都是物件。但 JavaScript 又不是一種真正的物件導向語言,因為它的語法裡面沒有 class(類別)。
43 |
44 | 在 JavaScript 中,我們要實作 OOP 有很多種方式,你可以使用 prototype、function 或者是 Object。但無論是哪一種途徑,其實都「不簡單」。
45 |
46 | 但 CoffeeScript 讓這件事變簡單了,比如以官網的這個例子:
47 |
48 |
49 | ~~~~~~~
50 | class Animal
51 | constructor: (@name) ->
52 |
53 | move: (meters) ->
54 | alert @name + " moved #{meters}m."
55 |
56 | class Snake extends Animal
57 | move: ->
58 | alert "Slithering..."
59 | super 5
60 |
61 | class Horse extends Animal
62 | move: ->
63 | alert "Galloping..."
64 | super 45
65 |
66 | sam = new Snake "Sammy the Python"
67 | tom = new Horse "Tommy the Palomino"
68 |
69 | sam.move()
70 | tom.move()
71 | ~~~~~~~
72 |
73 | 在往常不容易寫的漂亮的 JavaScript OO,可以被包裝得相當乾淨好維護。
74 |
75 |
76 |
--------------------------------------------------------------------------------
/manuscript/chapter-09-3.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Asset Pipeline 的架構
4 |
5 | Asset Pipeline 對於 assets 位置的定義。根據預設,你可以把 assets 放在以下三個資料夾內:
6 |
7 | * app/assets
8 | * lib/assets
9 | * vendor/assets
10 |
11 |
12 | 理論上,你把 assets 丟在這三個資料夾內,在 application.css / application.js 內 require 都可以動。
13 |
14 |
15 | ### 掛上其他 library
16 |
17 | 要引用 3rd party vendor assets,只要在 application.css 或者 application.js 進行 require 就可以使用了。
18 |
19 | ~~~~~~~
20 | //= require jquery
21 | //= require bootstrap
22 | ~~~~~~~
23 |
24 |
25 | ### 目錄夾的分類
26 |
27 | #### app/assets
28 |
29 | 在 Rails 3.1.x 之後的版本,rails g controler posts,會自動在 assets/styelsheets/ 和 assets/javascripts/ 中產生對應的 scss 與 coffeescript 檔案。所以 app/assets 是讓開發者放「自己為專案手寫的 assets」的地方。
30 |
31 | #### lib/assets
32 |
33 | lib 是 library 的簡寫,這裡是放 LIBRARY 的地方。所以如果你為專案手寫的 assets 漸漸形成了 library 規模,比如說 mixin 或者是自己為專案整理了簡單的 bootstrap,應該放在 lib/ 下。
34 |
35 | #### vendor/assets
36 |
37 | verdor 是「供應商」的意思,也就是 「別人寫的」assets 都應該放在這裡。比如說:
38 |
39 | * jquery.*.js
40 | * fanfanfan icons
41 | * tinymce / ckeditor
42 |
43 | 等等…
44 |
45 |
46 | ### Rails Asset Gem
47 |
48 | 透過 Asset Pipleline 的架構,開發者可以很容易透過 Gem 的機制,將 3rd party 的 CSS Framework 打包封裝,直接掛在 Rails 專案裡面使用。
49 |
50 | 比如說知名的 Fontawesome 這個 icon 專案,也有自己的 Rails gem
51 |
52 | 在 application.css 裡面掛上
53 |
54 | ~~~~~~~
55 | //= require font-awesome
56 | ~~~~~~~
57 |
58 | 這樣就可以直接使用了。不需要像以前開發網站一樣,把整個 framwork COPY 到專案裡面,既髒又難維護。
59 |
60 |
61 |
--------------------------------------------------------------------------------
/manuscript/chapter-09-4.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## Rails 4 with Asset Pipeline
4 |
5 | 在 Rails 4 上掛上 asset pipeline 跟 Rails 3 的方式有些許的不同。
6 |
7 |
8 | ### Rails 3
9 |
10 | 在 Rails 3 ,asset gem 基本上是被放在 asset 這個 group 的。
11 |
12 | ~~~~~~~
13 | group :assets do
14 | gem 'sass-rails', '~> 3.2.3'
15 | gem 'coffee-rails', '~> 3.2.1'
16 | gem 'uglifier', '>= 1.0.3'
17 | gem "compass-rails"
18 | end
19 | ~~~~~~~
20 |
21 | ### Rails 4
22 |
23 | 但在 Rails 4 上,這些 gem 卻要移出來不放在 asset 這個 group 裡。另外若要使用 compass 的話,因為一些技術問題,也需要安裝 1.1.2 以上的 gem,才能正常引用 compass
24 |
25 | ~~~~~~~
26 | gem 'uglifier', '>= 1.3.0'
27 | gem 'coffee-rails', '~> 4.0.0'
28 | gem 'sass-rails', '~> 4.0.0'
29 | gem "compass-rails", "~> 1.1.2"
30 | ~~~~~~~
--------------------------------------------------------------------------------
/manuscript/chapter-09.txt:
--------------------------------------------------------------------------------
1 | # 補充章節: Asset Pipeline
2 |
3 |
4 | Rails 在 3.1 之後,提供一套 Asset 開發流程,稱之 Asset Pipeline 。是一套讓開發者很方便能夠削減 (minify) 以及 壓縮 (compress) Javascript / CSS 檔案的框架。它同時也提供你直接利用其他語言如 CoffeeScript / SASS / ERB ,直接撰寫 assets ( 指的是 stylesheets / javascripts / images 這些靜態檔案) 的可能性。
5 |
6 |
7 |
--------------------------------------------------------------------------------
/manuscript/extra.txt:
--------------------------------------------------------------------------------
1 | # 附錄
2 |
3 |
4 | ## Resources of latest Ruby
5 |
6 | * [Ruby5](http://ruby5.envylabs.com/)
7 | * [Ruby Weekly](http://rubyweekly.com/)
8 | * [Ruby Inside](http://www.rubyinside.com/)
9 | * [RubyFlow](http://www.rubyflow.com/)
10 | * [RubyRogues](http://rubyrogues.com/)
11 | * [Thoughtbot Podcast](http://learn.thoughtbot.com/podcast)
12 | * [Railscast](http://railscasts.com/)
13 | * [Confreaks](http://www.confreaks.com/)
14 |
--------------------------------------------------------------------------------
/manuscript/how-to-use.txt:
--------------------------------------------------------------------------------
1 | # 本書使用方法
2 |
3 | 我自 2009 年以來,就開始訓練 Rails Developer。訓練方式是使用一系列題目,培養開發者對於 Rails 相關工具的熟悉度。這一本書是該系列題目的答案本。
4 |
5 | 這本書的前身,其實是一套訓練新進 Developer 的基礎教材。目的是希望能夠讓一個剛接觸 Rails 的開發者,快速熟悉 Rails 生態圈裡面的基本工具,以及 練熟 / 背熟 日常所需要用的知識。
6 |
7 | 我不建議各位讀者以「讀」的方式去使用這本書,相反地,我希望你動手實作。
8 |
9 | 解完這本書裡面所有的題目才是重點,你會在解題的過程裡面學到從無到有 build 起一個 Rails 網站,所需要的所有基本常識。
10 |
11 | 如果你真的解不開這裡面的題目,我才希望你看解答。
12 |
13 | 如果你偏好當面問也住在台北,我們 host 了一個每週二舉辦的 Rails Meetup ,歡迎帶著你手邊的問題來這裡問。這邊的同好對於新手都很友善,會十分樂於回答你的問題而已。
14 |
15 | 如果你不住在台北,我開了一個 Facebook 社團 ,這裡可以詢問本書相關的問題。
16 |
17 | 發問請貼程式碼,可將程式碼貼在 [Gist](http://gist.github.com) 上,再轉貼到論壇上發問。
18 |
19 |
20 | ### 本書程式碼
21 |
22 | 本書程式碼放在:
23 |
24 | ### 開發環境
25 |
26 | 我力求提供讀者一個能夠上手的 Rails 開發環境。但是要能夠建築出一個開發 Rails 的環境,變因實在太大。從
27 |
28 | * MacOS 的版本 10.7, 10.8, 10.9 的 command line 工具安裝方式與位置都不同
29 | * Debian 5,6 / Ubunbtu 10,11,12 的 apt-get 安裝 lib 位置與相依都不同
30 | * Ruby 從 1.8.7 -> 1.9.3 -> 2.0.0 的語言變更導致 library 不相容
31 | * Rails 從 3.0 -> 3.1 -> 3.2 到 4.0.0 rc1, beta1, official 的官方配置與 deploy 環境都不同
32 |
33 | 如果本書提供的環境安裝方式無效,請儘量試試在 [Stackoverflow](http://stackoverflow.com/) 上找找,也許很快就能解決你的問題。
34 |
--------------------------------------------------------------------------------
/manuscript/images/RESTful.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/RESTful.jpg
--------------------------------------------------------------------------------
/manuscript/images/Ruby-on-Rails-process.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/Ruby-on-Rails-process.jpg
--------------------------------------------------------------------------------
/manuscript/images/bird_32_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/bird_32_gray.png
--------------------------------------------------------------------------------
/manuscript/images/bird_32_gray_fail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/bird_32_gray_fail.png
--------------------------------------------------------------------------------
/manuscript/images/code_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/code_bg.png
--------------------------------------------------------------------------------
/manuscript/images/dotted-border.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/dotted-border.png
--------------------------------------------------------------------------------
/manuscript/images/email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/email.png
--------------------------------------------------------------------------------
/manuscript/images/ex0-browser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex0-browser.png
--------------------------------------------------------------------------------
/manuscript/images/ex0-hello-world.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex0-hello-world.png
--------------------------------------------------------------------------------
/manuscript/images/ex0-pow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex0-pow.png
--------------------------------------------------------------------------------
/manuscript/images/ex0-route.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex0-route.png
--------------------------------------------------------------------------------
/manuscript/images/ex1-list-board.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex1-list-board.png
--------------------------------------------------------------------------------
/manuscript/images/ex1-nest-list-board.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex1-nest-list-board.png
--------------------------------------------------------------------------------
/manuscript/images/ex1-nest-new-post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex1-nest-new-post.png
--------------------------------------------------------------------------------
/manuscript/images/ex1-new-post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex1-new-post.png
--------------------------------------------------------------------------------
/manuscript/images/ex2-current_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex2-current_user.png
--------------------------------------------------------------------------------
/manuscript/images/ex2-general.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex2-general.png
--------------------------------------------------------------------------------
/manuscript/images/ex2-signin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex2-signin.png
--------------------------------------------------------------------------------
/manuscript/images/ex2-signup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex2-signup.png
--------------------------------------------------------------------------------
/manuscript/images/ex4-board-show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex4-board-show.png
--------------------------------------------------------------------------------
/manuscript/images/ex4-default-scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex4-default-scope.png
--------------------------------------------------------------------------------
/manuscript/images/ex4-recent-scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex4-recent-scope.png
--------------------------------------------------------------------------------
/manuscript/images/ex4-will-paginate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex4-will-paginate.png
--------------------------------------------------------------------------------
/manuscript/images/ex5-upload-form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex5-upload-form.png
--------------------------------------------------------------------------------
/manuscript/images/ex5-upload-result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex5-upload-result.png
--------------------------------------------------------------------------------
/manuscript/images/ex6-seed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex6-seed.png
--------------------------------------------------------------------------------
/manuscript/images/ex7-github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/ex7-github.png
--------------------------------------------------------------------------------
/manuscript/images/group-scaffold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/group-scaffold.png
--------------------------------------------------------------------------------
/manuscript/images/helloworld.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/helloworld.png
--------------------------------------------------------------------------------
/manuscript/images/line-tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/line-tile.png
--------------------------------------------------------------------------------
/manuscript/images/noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/noise.png
--------------------------------------------------------------------------------
/manuscript/images/post-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/post-new.png
--------------------------------------------------------------------------------
/manuscript/images/rails-101-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/rails-101-cover.png
--------------------------------------------------------------------------------
/manuscript/images/rails-init.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/rails-init.png
--------------------------------------------------------------------------------
/manuscript/images/rss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/rss.png
--------------------------------------------------------------------------------
/manuscript/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/search.png
--------------------------------------------------------------------------------
/manuscript/images/title_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xdite/rails-101/f63ee546bad54c166f5924204ef6948ba3a3deef/manuscript/images/title_page.png
--------------------------------------------------------------------------------
/manuscript/install-on-mac.txt:
--------------------------------------------------------------------------------
1 | # Ruby on Rails 安裝最佳實踐
2 |
3 |
4 | ### 打造 Bug Free 的 Rails 開發環境
5 |
6 | 許多新手初入門 Rails,除了對 Rails 版本號更迭過快感到十分抓狂,開發環境的建置也常令人一個頭兩個大。不是卡在 lib 編不過,就是 gem 抓不到。
7 |
8 | OSX 10.8、MySQL、ImageMagick、readline ....
9 |
10 | project 還沒開始寫半行,先被困在莫名其妙的套件相依性上。有沒有比較簡單且不容易踩中蟲的安裝步驟呢?有。
11 |
12 | 我們公司 [Rocodev](http://rocodev.com) 寫了一份 Ruby on Rails 安裝最佳實踐,這份教學幾乎是 Bug Free。
13 |
14 |
15 | I> ## 最新版本
16 | I>
17 | I> 最新版本在此:
18 | I> 如果書中的內容過期,請到此頁面找尋最新版解法。
19 |
20 |
21 |
22 | ### Mac 是最好的 Ruby on Rails 開發環境,馬上買一台!
23 |
24 | 看到標題,也許你心裡浮出了問號?我只是想試看看 Rails,有必要這樣大手筆的購買設備嗎?
25 |
26 | 有!如果你立志相成為一個 Rails Developer 的話。
27 |
28 |
29 | 世界上絕大多數的 Rails Developer 開發都是使用 MacBook +。這不僅僅只是 Best Practices / 神兵利器( brew、Livereload ...)只存在 Mac 的關係。更重要的是每當 OS 更新、Gem 版本更新、 Rails 地雷 ...
30 |
31 | Linux 使用者都會因為 package system maintainer 不是 Rails Developer 而不熟生態圈的關係,被雷炸的像次等公民。
32 |
33 | 開發者的時間就是金錢,想想好的設備會為你的生產力帶來多大的改善?好的 Framework 會多節省你的開發時間?
34 |
35 | 你都已經打算開始學習 Rails 改善你悲慘的開發人生了?為什麼不買一台 Mac 讓自己更節省力氣呢?
36 |
37 |
38 | {::pagebreak :/}
39 |
40 | ## 安裝步驟
41 |
42 |
43 | `強烈警告:請絕對不要跳著裝!如果疏漏步驟有可能導致無法復原需要重灌。`
44 |
45 | ### 系統套件
46 |
47 | 1. 進行 Mac 系統更新到最新版 10.9
48 | 2. 從 Apple Store 上取得 Xcode 5.0.4 安裝 (內建 Command Tools )
49 |
50 | ### 安裝 Homebrew
51 |
52 | ~~~~~~~~~~~~~~~
53 | $ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
54 | $ brew install git
55 | $ brew update
56 |
57 | $ brew tap homebrew/dupes
58 | $ brew install apple-gcc42
59 | ~~~~~~~~~~~~~~~
60 |
61 | ### 安裝 XQuartz
62 |
63 | 安裝 ImageMagick 需先有 X11 的 support,OSX 10.8 拿掉了...
64 |
65 |
66 |
67 |
68 | {::pagebreak :/}
69 |
70 |
71 | ### ImageMagick / MySQL
72 |
73 | #### 安裝 Imagemagick
74 |
75 | ~~~~~~~~~~~~~~~
76 | $ brew install imagemagick
77 | ~~~~~~~~~~~~~~~
78 |
79 | #### 安裝 MySQL
80 |
81 | ~~~~~~~~~~~~~~~
82 |
83 | $ brew install mysql
84 | $ unset TMPDIR
85 | $ mysql_install_db --verbose --user=`whoami` --basedir="$(brew --prefix mysql)" --datadir=/usr/local/var/mysql -- tmpdir=/tmp
86 | $ mysql.server start
87 | $ mysqladmin -u root password '123456'
88 | $ mkdir -p ~/Library/LaunchAgents
89 | $ find /usr/local/Cellar/mysql/ -name "homebrew.mxcl.mysql.plist" -exec cp {} ~/Library/LaunchAgents/ \;
90 | $ launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist
91 |
92 | ~~~~~~~~~~~~~~~
93 |
94 | 若無法順利進行安裝,可換下載 [MySQL官網](http://dev.mysql.com/downloads/mysql/) 上的 Mac OS X ver. 10.6 (x86, 64-bit), DMG Archive 來安裝 MySQL。
95 |
96 |
97 | {::pagebreak :/}
98 |
99 |
100 | ### 安裝 RVM 與 Ruby 2.0
101 |
102 | 在建制 Rails 環境的時候,我們可能會有跑不同版本的 Ruby 或者不同的 getsemt 的需求。[Ruby Version Manager](https://rvm.beginrescueend.com/) 是一個能夠讓我們用很優雅的方式切換 Ruby 版本的工具。同時使用系統 Ruby,其實很容易弄髒環境和產生一些靈異現象的 bug。於是我們在建制環境時,通常第一時間就會裝起 RVM。
103 |
104 | #### 安裝 RVM
105 |
106 | ~~~~~~~~~~~~~~~
107 | $ bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
108 | $ . ~/.profile
109 | $ source ~/.profile
110 | ~~~~~~~~~~~~~~~
111 |
112 |
113 | #### 安裝 Ruby 2.0
114 |
115 | ~~~~~~~~~~~~~~~
116 | $ brew install libyaml
117 | $ rvm pkg install openssl
118 | $ rvm install 2.0.0 \
119 | --with-openssl-dir=$HOME/.rvm/usr \
120 | --verify-downloads 1
121 | $ rvm use 2.0.0
122 | ~~~~~~~~~~~~~~~
123 |
124 |
125 | I>## 注意事項
126 | I>
127 | I> 使用 RVM 安裝 gem 和 passenger-install-apache2-module 不需要加上 sudo , 因為使用 sudo 會使用非 RVM 的 ruby 環境, 安裝目錄也不一樣.)
128 |
129 |
130 |
131 |
132 |
133 | ### 安裝必要 Ruby gems
134 |
135 | ~~~~~~~~~~~~~~~
136 | $ gem install rails --version 4.0.0
137 | $ gem install mysql2
138 | $ gem install capistrano
139 | $ gem install capistrano-ext
140 | ~~~~~~~~~~~~~~~
141 |
142 | {::pagebreak :/}
143 |
144 |
145 | ### 設定 HTTP Server (使用 Pow)
146 |
147 | #### 使用 Pow 作為 HTTP Server
148 |
149 | [Pow](http://pow.cx) 是 [37 Signals](http://37signals.com/) open-source 出來的一套 Rack Server。其標榜的就是 Zero Config。
150 |
151 | Pow 的原理原理是攔截 routing,導到 Pow 上。所以新增 project 不需要更改 /etc/hosts 就會生效。也因為 Pow 是 rack-based,支援 [rack](http://rack.rubyforge.org/) 的 framework 掛了就能跑。
152 |
153 |
154 | 相較起來,以往的 [Passenger](http://www.modrails.com/) 搭配 Mac 本機端的 apache 的 solution 就顯得太笨重了。
155 |
156 | #### Installation
157 |
158 | [Pow](http://pow.cx) 的安裝相當簡單。
159 |
160 | ~~~~~~~~~~~~~~~
161 | $ curl get.pow.cx | sh
162 | ~~~~~~~~~~~~~~~
163 |
164 | 即完成安裝。
165 |
166 | #### Setting
167 |
168 | [Pow](http://pow.cx) 預設的目錄是在 ~/.pow 下。
169 |
170 |
171 | 因此若要讓 project 跑在 Pow 之下。以我的 wiki 為例 :
172 |
173 | ~~~~~~~~~~~~~~~
174 | $ cd ~/.pow/
175 | $ ln -s ~/projects/wiki
176 | ~~~~~~~~~~~~~~~
177 |
178 | 打開瀏覽器,輸入 http://wiki.dev 就完成了。以往的 http://localhost:3000/ 實在太噁心了,別再用它了!
179 |
180 | T> 小技巧
181 | T>
182 | T> 或者是直接在 project/wiki 下打 `powder link` 也可以。
183 |
184 |
185 |
186 |
187 |
188 | #### 使用 Powder 管理 Pow
189 |
190 | [Powder](https://github.com/Rodreegez/powder) 是後來衍生出來的一套管理工具。
191 |
192 | 因為 Pow 的管理有點不易,所有有人寫了這個工具把一些常用的功能包裝起來。
193 |
194 | 安裝方法:
195 |
196 | ~~~~~~~~~~~~~~~
197 | $ gem install powder
198 | ~~~~~~~~~~~~~~~
199 |
200 | 通常我只拿來做 `powder restart` 和 `powder log` 而已。
201 |
202 | I> ## 注意事項
203 | I>
204 | I> 若電腦預設非使用系統 Ruby 的開發者需注意此點。Pow 很可能會抓到系統 Ruby 及其 gemset 而無法啓動。我個人的解法是安裝 RVM 管控 Ruby,再在欲使用 Pow 之 project 目錄放置 .rvmrc 即可。
205 |
206 |
207 |
208 |
209 | .rvmrc 內容如下:
210 |
211 | ~~~~~~~~~~~~~~~
212 | rvm 2.0.0
213 | ~~~~~~~~~~~~~~~
214 |
215 |
--------------------------------------------------------------------------------
/manuscript/preface.txt:
--------------------------------------------------------------------------------
1 | # 如何排除障礙
2 |
3 | 1. 加入 https://www.facebook.com/groups/rails101/ FB 社團。在社團上問問題
4 | 2. 在 Google 或 Stack Overflow 上用錯誤訊息關鍵字找答案。
5 | 3. 你是不是忘了加 `@` 呢?
6 | 4. 你是不是忘了跑 migration 呢?
7 | 5. 你是不是忘記加欄位呢?
8 | 6. 你是不是忘記 touch tmp/restart.txt 呢?
9 | 7. 在 http://guides.rubyonrails.org 找線索
10 | 8. 檢查 development.log 或者是 Chrome 的 DevTools Console
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/manuscript/recap.txt:
--------------------------------------------------------------------------------
1 | {::pagebreak :/}
2 |
3 | ## 總複習
4 |
5 | 到目前為止。這本書提到的主題有
6 |
7 | * rvm
8 | * pow
9 | * CRUD
10 | * RESTFUL
11 | * 雙層 RESTFUL
12 | * 登入(Devise)
13 | * Scope
14 | * Helper
15 | * Partial
16 | * Capistrano
17 | * Asset Pipeline
18 |
19 | 這本書原始的想法,其實是把一個初學者需要自學「一年」(我沒有開玩笑)的主題,濃縮到幾個禮拜。所以基本上這本書不能用「看」的,必須要動手實作。並且重複練習 3 次以上。
20 |
21 | 所以看到這裡,還請你重新再來一次。(認真)
22 |
23 | 不過這次你可以嘗試不一樣的作法。
24 |
25 |
26 | #### 第二遍
27 |
28 | 每個 chapter 都開一個 git branch 實作。並且推到 Github 上。
29 |
30 | #### 第三遍
31 |
32 | 幫 Groupme 加上 admin 後台,以及其他功能。並且推到 Github 上。
33 |
34 | 相信做到第三遍之後,你應該可以自然而然看懂原本像天書一般的 Rails API。
35 |
36 |
--------------------------------------------------------------------------------
/manuscript/requirement.txt:
--------------------------------------------------------------------------------
1 | # 學習 Rails 前置所需要的技能
2 |
3 | 經過這些年的 Rails Developer 培訓之後,我強烈建議開始學習 Rails 之前,請先學會以下相關技能:
4 |
5 | * Git
6 | * 熟習 Vim 以及 SublimeText2 其中一種開發工具
7 | * 熟悉 Linux Command Line 的操作
8 |
9 | ## Git 學習資源
10 |
11 | Codeshool 出了三套 Git 課程。請先練習過後,再開始學習 Rails 開發,我相當不建議讀者跳過 Git 技巧的練習。
12 |
13 | * TryGit
14 | * GitReal
15 | * GitReal2 (進階技巧,可以之後再學)
16 |
17 |
18 | X>## 練習作業
19 | X>
20 | X> 1. 上 [Github](https://github.com) 註冊一個帳號
21 | X>
22 | X> 2. 練習以下 Git 指令
23 | X>
24 | X> * git commit
25 | X> * git push
26 | X> * git pull
27 | X> * git branch
28 | X> * git checkout
29 | X> * git merge
30 |
31 | {::pagebreak :/}
32 |
33 | ## 編輯器學習資源
34 |
35 | ### Vim
36 |
37 | * c9s 的 [Vim Hacks](http://c9s.blogspot.com/2009/08/vim-hacks-coscup.html) 是相當好的 vim 學習資源
38 |
39 | ### Sublimtext 2
40 |
41 | * Sunlimtext 2 下載網址:
42 |
43 | * [Sublime Text 台灣](https://www.facebook.com/SublimeTextTW) 社群最近也撰寫了一本相當不錯的[SublimeText 學習手冊](http://docs.sublimetext.tw/)
44 |
45 | ## Linux Command Line 學習資源
46 |
47 | * PeepCode 的 [Meet the Command Line](http://peepcode.com/products/meet-the-command-line)
48 | * PeepCode 的 [Advanced Command Line](http://peepcode.com/products/advanced-command-line)
49 |
50 |
51 | X>## 練習作業
52 | X>
53 | X> * 練習 [Meet the Command Line](http://peepcode.com/products/meet-the-command-line)
54 | X> * 練習 [Advanced Command Line](http://peepcode.com/products/advanced-command-line)
55 |
56 |
57 |
--------------------------------------------------------------------------------