[v] xdite rails 第一週 開發實作

建立後台

  • 列出目前所有的 routes

http://guides.rubyonrails.org/routing.html

  1. terminal: $ rake routes
  2. 瀏覽器: localhost:3000/rails/info/routes
  3. 瀏覽器: 跟路徑錯誤有關的錯誤訊息中
  • product_params 是某種 rails 的命名慣例嗎?

No, 這是一個method, 所以可以自由命名,而這邊是命名給 strong parameters.

routes.rb
namespace :admin do
  resources :products
end

這個的意思是,在 admin 下的 products controller 中用 rails 內建的指令resources :products一行設定好 products controller 內七大 CRUD actions, which are new, create, edit, update, show, index, destroy.

  • 開一個新的 branch 重新練習的時候,db 並不會清空,怎麼辦?
$ rake db:drop       # 把資料庫丟掉
$ rake db:create  # 建立資料庫   
$ rake db:migrate # migrate資料庫 
$ rake db:seed        # 把seed內的資料灌進db (不需要再 rake db:migrate 一次)

這樣做會把之前建立的資料清除,然後在 db/seeds.rb 裡寫種子檔,重建時將一些基本資料存進去(例如使用者資料, 基本商品資料, 基本文章...etc )。Rails101 第十章則進一步把上述四個指令簡化成一個指令 rake db:rebuild

  • namespace 是什麼?

用來做區隔的標籤

  • 在 form 中輸入資料按下 submit 把資料送出到 DB,如果沒有 throw exception, 那是不是資料成功送進 DB 的步驟就等於 save 了?

小蟹:
不太對。沒有 throw exception 只能說程式沒有出錯
比方說 validate 失敗,並不會 throw exception,但也沒有存進 DB
如果你忘了寫 save,也不會 throw exception,而且沒有存進 DB

我:
那麼比方說我們寫create action時,

def create
  @xxx = Xxx.new(xxx_params)
  if @xxx.save
    redirect_to "/"
  else
    render :new
  end
end

是什麼步驟有 save, 我們才會去寫 if @xxx.save ?

小蟹:
最常見是 validate
會隨著 save 執行的事件

我:
http://guides.rubyonrails.org/active_record_validations.html

"The following methods trigger validations, and will save the object to the database only if the object is valid."
create
create!
save
save!
update
update!

整理一下我的理解:

  1. 因為是 create method, 所以會 "trigger validations, and will save the object to the database only if the object is valid."
  2. 因此有寫 if @xxx.save 的條件的空間
  3. 也就有後續 “redirect_to” 或 “render" 的情形。

實作使用者功能

  • rails g devise:install 這個指令是做什麼的?

devise 的一個指令。gem 只把 devise 裝進 rails, 但如果要在這個專案執行,仍然要 devise:install

  • rails g devise user 後程式跑的內容代表什麼意思?它和 rails g model user 生出來的 user 有什麼差別?

$ rails g model pie

invoke    active_record
create    db/migrate/20160312075723_create_pies.rb
create    app/models/pie.rb
invoke    test_unit
create    test/models/pie_test.rb
create    test/fixtures/pies.yml

pie model內容:

class Pie < ActiveRecord::Base
end

$ rails g devise cake

invoke    active_record
create    db/migrate/20160312075647_devise_create_cakes.rb
create    app/models/cake.rb
invoke    test_unit
create    test/models/cake_test.rb
create    test/fixtures/cakes.yml
insert    app/models/cake.rb
route     devise_for :cakes

cake model內容:

class Cake < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end

routes.rb內容:

Rails.application.routes.draw do
  devise_for :cakes
end
  • 如果不小心把 $ rails g devise user 寫成 $ rails g devise users,生出的檔案都一樣,不會有影響。

管理者存取後台設定

  • current_user 是 rails 內建的嗎?

No, 是 devise 給的。

  • 怎麼規劃架構和功能建置的流程?

就是用 user story 來切,切得適當就能規劃出好的功能及開發流程。這需要經驗就是了。

  • slide 3/10中,導向登入頁面是 devise 的功能嗎?那這個登入的 view 是在哪裡?

是 devise 的功能

$ rails g devise:views

devise 的 view 就會出現在 app/views/devise 資料夾了

  • 用 devise 做的登錄畫面要怎麼顯示中文?

https://github.com/plataformatec/devise/wiki/I18n

  • 哪些順序有影響?

gem 的順序 => 沒影響
def 的順序 => 沒影響
多個 before_action 的順序 => 有影響
method 中 code 順序 => 有影響
yml 的縮排 => 有影響
require
include

  • require 的順序?

[這段不確定] require xxx, include xxx 一般是放最上面,因為如果有段內容要用到 require xxx, include xxx, 結果 require xxx, include xxx 不在該段內容之前,就會跳出錯誤。

  • 為什麼 User model 中,def admin? 裡的 is_admin 可以直接對應到 db 中 user table 的 is_admin 欄位?是 rails magic嗎?

是rails magic 沒錯。這邊rails會先去找同一個 model 裡面同名的 method, 沒找到就再去找 DB 欄位。

以這個 MVC 架構而言,controller 跑完去 model 找 admin?, 沒找到所以要新增 def admin?, model 的部份處理好了,再往 database 去找 is_admin 欄位,沒找到所以又跳出錯誤訊息,於是要再新增 is_admin 欄位。在 rails 中,model 欄位直接對應 database 中 form 的欄位。

  • add_column :users, :is_admin, :boolean, default: false
  1. add_column => add column
  2. :users => table name
  3. :is_admin => column name
  4. :boolean => column type

在 console 建立使用者並改為 admin 權限

  • 為什麼rails console可以更改權限?

rails console 有造物主般的權限,所以可以新增管理者、使用者......等,但實務上不會在 console 手動新增,而是寫個包裹(這邊用詞正確嗎?)自動執行。原因是手動可能會出錯,寫包裹自動執行不會出錯,因為在執行前都可以先在 development 環境測試包裹內容正不正確。

解決不能登出的問題

  • 為什麼登入/登出是寫在 navbar.html.erb(前端)而不是寫在 controller? 第二週購物車商品數量更改也寫在 carts/index.html.erb, 在 view 改數量,為什麼能影響到後端更動 cartitems.quantity?

以更改數量為例

<%= form_for item, url: item_path(product) do |f| %>
  <%= f.select :quantity, 1..product.quantity %>
  <%= f.submit "更新", data: {disable_with: "Submitting..."} %>
<% end %>

那段內容包在 form_for 裡面,所以按鈕不只是按鈕,實際上也是 form 表單。當我們選定數字後,按下”更新”就會送出到它對應的 item_path(product),但是在 routes.rb resources :items, controller: "cart_items",我們已經先定義這個 item 是指向 cart_items controller, 所以前面按下更新後,實際上是對 cart_item 這個資料送出更動的 request, 這個 request 是對應到 cart_items controller 中的 update method, 所以開始跑 update method, 並進而在條件符合的情況下,跑到這一行 @item.update(item_params) 而更動到 cart_items 表單中的 quantity 欄位。

  • [提問] 為什麼這個 request 是 update method?

因為在 <%= form_for item, url: item_path(product) do |f| %> 裡面的 product 是一個 model: Product 的物件,該物件如果是空的 (e.g.: Product.new ) 就會幫你設定打到 create action 的 path,如果是有東西 就會幫你設定打到 update action 的 path。這樣的好處是,form 這邊只要判斷變數的內容,即可分辨出是要 create 還是 update, 我們只需要將物件設定好 ( 像是在 controller 裡面定義 ) 一份 view 就可以搞定。( views/admin/products/new 跟 edit 幾乎一模一樣就是這個原理 )

  • <%= f.submit "更新", data: {disable_with: "Submitting..."} %>

disable_with 的動能是避免重複點選按鈕,"Submitting..." 則是按下按鈕觸法 disable_with 後,按鈕上顯示的內容。整個流程是,按下顯示為"更新"的 submit 按鈕後,觸法 disable_with 功能,按鈕進入無法被按(所以不會重複點擊)的狀態,並且 "更新" 字樣轉為 "Submitting..." 字樣。

  • 拆解 <%= link_to("登出", destroy_user_session_path, method: :delete) %>

link_to 後面依序是:

  1. 按鈕上的文字(“登出”)
  2. 路徑(destroy_user_session_path)
  3. HTTP verbs 每個動詞在 spec 中都有定義使用情況,DELETE 就是 for 刪除的行為。所有的 _path 都只是幫你生出網址, :method 非預設 :get 的話都要自己指定。
(原本部份觀念錯誤的3,留紀錄供參考對照。)
一般就是以上兩種,至於第三個(method: :delete)則是指定某個 method, 這是 devise 的功能,也可以改成:get. 但為什麼要指定 DELETE 呢?因為比如說,今天你收到一個電子郵件好了,有人在電郵中的一張圖片上包一段程式碼是讓你的帳號登出,一旦圖片顯示出來(which is 執行了GET), 你的帳號就被登出了。What if the code is to delete all your files? That would be a big problem. 類似的情況則是bot可以不透過按“登出”鈕就登出,或者看指令是什麼而有對應的行為。今天設定method: :delete, 則是真人按下”登出”鈕時執行登出的動作。

圖片的例子不成立,因為圖片屬於 static file,靜態檔案並不會有 GET 以外的行為。正常的網址都要點擊後才會生效,所以不會發生圖片顯示後就觸發某個動作這種事情。

  • 設定 method: :delete 是真人按下”登出”鈕時、還是"確定登出"的對話框時執行登出的動作?

只有在按下"確定登出"的 yes 鈕時,才會送出request.

  • 比如說 <%= link_to("登出", destroy_user_session_path, method: :delete ) %>destroy_user_session_path不就已經代表是用DELETE了?為什麼後面還要一個method: :delete? 而且從rake routes也可以得知只有一個 destroy_user_session_path,並且後面標示Verb是DELETE,不就沒有跟其他verbs共用 destroy_user_session_path 的可能嗎?那為什麼還要特別註明是用 method: :delete ?
  1. 不對,所有的 _path 都只是幫你生出網址, :method 非預設 :get 的話都要自己指定。
  2. 後面那個問題,現在沒有verbs共用同一個路徑不代表以後沒有,所以還是要寫清楚。
  • 全域的layout和partial有什麼關係?

可以把partial想成某個配件,把程式碼整理成partial有三大好處:

  1. 全域的layout(即layout/application.html.erb)會清爽許多。
  2. 每個partial可以分別去設計,利於分工。
  3. 可以重複使用。

將頁面用 bootstrap 套版

  • 拆解下面的內容,bootstrap-sprockets 是做什麼的?
@import "bootstrap-sprockets";
@import "bootstrap";

Deploy 到 heroku (投影片)

  • 跑完下列指令後
$ git add -A
$ git commit -m "message"
$ git push heroku store-v1:master
$ heroku run rake db:migrate

接著跑$ heroku run rake db:seed, 不需要再跑一次$ heroku run rake db:migrate就可以生效。

Misc.

  • 由於回家作業中,有些新功能不完全照著user story來開發,比如說”解決不能登出的問題”就是user story中沒有明確寫到的,請問是什麼情況?是因為開發時常會遇到user story寫得不夠完整而要隨時調整user story內容嗎?我們要怎麼判斷是不是把初步的功能做齊了呢?又,在web developing的領域,我們是透過快速迭代來解決這樣的問題,還是找一組人對web app做產品測試?還是有其他解法?

這種情況是user story寫得還不夠細。這份教材的user story大概到V6而已,xdite開發產品時實際上會一口氣寫到V10, 把大致上的流程、目標、要解決的問題、風險儘量抓出來。風險是指比如說要接信用卡,首先你要有公司才能去接第三方支付,第三方支付中,A付寶的審核可能只需2天,B付寶卻可能拖3個月,如果這個風險沒抓出來,就會變成開發時的bottle neck。

而user story寫詳盡,開發時比較會有大局觀,不至於為了開發一個不重要的小功能,造成重要功能進度delay,因小失大。另外,每個story都可以估大概要做多久,就算估不出來,也可以去估每個小的story大概做多久,並估出這個story大概要花多少的人力。

user story以專案管理的角度來說,是為了完成有價值的東西,”解決不能登出的問題”實際上是個技術bug, 雖然還是要解決,但沒那麼重要,可以先放在Agile/Scrum概念中的Epic(可以想成是待解決技術清單),等有價值的東西先做完了再解決。這類問題的確是用快速迭代的方式解決,xdite的作法是星期一到星期四focus在寫story上的東西,星期五修Epic。大部分公司不會有錢請另一組人做產品測試,所以一般是產品開發完,自己要去做測試,這也是放星期五。主要是先把大的、小的user story順好、骨架搭起來,最後再驗收。Agile的驗收很頻繁,可能每兩個禮拜一次,驗收這個sprint該完成什麼事情、沒完成的話原因是什麼?(比如小story沒補上?epic沒修好?)

  • 如果app開發到一半去改Gemfile中的Rails版本然後bundle install會發生什麼事?

google search “semver”(語意上的版本)主要版本、次要版本、小版本。

小版本更新通常是一些fixes, 不影響專案的架構,比如3.2.1 → 3.2.5,可以放心升,但即便如此,通常還是建議不要這樣做,而是上線前大修一次;而像3.2.1 → 3.2.11,可能中間間隔10個月了,升的時候要小心;如果像是4.0.1 → 4.1.1,有些中等程度的API可能會變更/棄用,升上去Rails可能只會給警告,或是小地方壞掉,但假如是一次跳兩個版本,比如4.0.1 → 4.2.0,不做出相對的修正,app可能就直接炸掉。

這是Rails的特性,或者說是維護Rails的37Signals團隊的風格,他們覺得不好的寫法就會在升級時改掉,rails developer得要一直追這些寫法、規則。以Rails而言,一個主要版本到下一個主要版本,比如說Rails 3 → Rails 4,大概是18個月。

另個跟rails版本有關的是gem的支援,版本變動造成的Rails內部的API變動,可能會連帶造成有些gems不支援新版本的Rails。一是等作者更新gem, 一是專案開發者自己修,但junior developer通常不具備這個能力。

  • User story 寫到最後會變成很可怕的量,如何限縮到MVP的範圍?(或,如何從中找出MVP?)

分兩個角度看,創業基本上是MVP → product,不會有story太多要去找出MVP的情況。(如果有,那其實創業者沒釐清自己產品的核心,或者誤解了MVP的概念。)

如果加入公司,實務上老闆常會提出太不合理的要求、或者PM常常會寫出太多沒用的story,而RD確實需要聚焦在值得實作的story上,大家都有自己對要求、story的想法,但是資源有限,這時可以用5 Whys來分析值不值得實作。5 Whys其實就是一種辯證法,透過前後問題的因果關係一層一層問下去,慢慢逼近story的本質,最終讓團隊對story有清楚而一致的理解。這種方法並不局限於5,必要的話也可以問得更多(或更少)。

  • namespace能達到的目的,scope似乎也可以做到(除了path不一樣),兩者合適的用法是?

scope可以寫到很複雜,namespace可以想成是簡單版的scope

  • $ rake db:migrate:status 可以查 migration 狀態,前面顯示 up 就代表已經 migrate 了。

  • migration 有誤怎麼解?

  1. $ rake db:rollback -> 進 migration 檔更改內容 -> rake db:migrate
  2. 如果 migration 已經上線(production階段),新增一個 migration 檔(rails g migration) -> 寫上要修改的內容 -> $ rake db:migrate 的做法會比較適合。
  3. 補充觀念:Rails 會記錄 migration 檔名中的 timestamps 來判斷你已經對資料庫操作過哪些 migrations, 跑過的 migrations 就不會重複跑(不考慮rake db:rollback的情況)。Rails 透過資料庫中的 schema_migrations 這張 table 來記錄已經跑過哪些 migrations。

http://edgeguides.rubyonrails.org/active_record_migrations.html
http://guides.rubyonrails.org/v3.2.17/migrations.html
https://gist.github.com/amejiarosario/2950888
https://ihower.tw/rails4/basic.html

  • 如何 rollback 好幾步?
$ rake db:rollback STEP=n

可搭配

$ rake db:migrate:status

可以顯示目前 migrations 執行的情況,會有 Status, Migration ID(timestamp), Migration Name 三組資料。