service
要使用別的功能的時候可以把method定義在service裡面
不過這邊其實我沒作筆記囧
參考一下
routes
後面接path的話可以方便改變網址,而且不用特別改link_to裡面的path
resources :candidates, path: 'users'
Prefix Verb URI Pattern Controller
root GET / welcome
candidates GET /users(.:format) candidates
POST /users(.:format) candidates
new_candidate GET /users/new(.:format) candidates
edit_candidate GET /users/:id/edit(.:format) candidates
candidate GET /users/:id(.:format) candidates
PATCH /users/:id(.:format) candidates
PUT /users/:id(.:format) candidates
DELETE /users/:id(.:format) candidates
:id
其實我最一開始不知道什麼時候才要在path後面加東西,後來才瞭解到網址後面有id的都需要,其實寫candidate.id也是一樣的,但因為rails會幫忙把id抽出來,所以寫短一點的就可以了
<% @candidates.each do |candidate| %>
<li>
<%= link_to candidate.name, candidate_path(candidate) %>
<%= link_to
<%= link_to
<%= candidate.party %>
<%= candidate.age %>
<%= candidate.politics %>
</li>
<% end %>
candidate_path(candidate)可以只寫candidate就好,只把物件給rails也會自己判斷,但我個人會因為搞不清楚我在幹嘛,所以會寫完整的。(編輯的path不能改)
實際上html會變成下面這樣,都會變成data-*, 所以method其實放在data裡面也可以(只是也沒必要),或者想要另外傳一些資料進去的時候也可以寫在data裡面
<a data-confirm="刪除嗎??" rel="nofollow" data-method="delete" href="/candidates/1">刪除</a>
path
因為下面那幾個的path是共用的,所以刪除資料的時候記得後面method要說是delete,不然會跑到show,這也是我當初常用錯的東西
candidate GET /users/:id(.:format) candidates
PATCH /users/:id(.:format) candidates
PUT /users/:id(.:format) candidates
DELETE /users/:id(.:format) candidates
find / find_by
Candidate.find_by(id: 1)
Candidate.find(1)
上面這兩個找得到id的時候得到的結果是一模一樣的,但找不到的時候的情形會不一樣,看你喜歡nil,還是喜歡處理例外
會回傳nil
Candidate.find_by(id: 999)
丟給你ActiveRecord::RecordNotFound: Couldn’t find Candidate with ‘id’={:id=>999}
Candidate.find(999)
和new是可以共用的,而且畫面上會出現已經填好的資料(驚嚇),因為rails會去判斷你丟給form_for的是新的物件還是從資料庫抓出來的
(不過前提是要new和edit的變數名稱要取一樣的)
然後生出來的html長這樣,雖然method是寫post,不過因為是要更新資料,所以會偷偷幫你模擬用patch(瀏覽器實際上沒有支援patch,所以都是用模擬的)
<form class="edit_candidate" id="edit_candidate_1" action="/candidates/1" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓"><input type="hidden" name="_method" value="patch">
partial
盡量不要在partial裡用實體變數
比方說最一開市像下面這樣子寫
# new / edit
<%= render 'form' %>
#_form.html.erb
<%= form_for @candidate do |f| %>
姓名: <%= f.text_field :name %><br/>
政黨: <%= f.text_field :party %><br/>
年紀: <%= f.text_field :age %><br/>
政見: <%= f.text_area :politics %><br/>
<%= f.submit %>
<% end %>
雖然上面那樣也會動,因為他會自己去抓@candidate,但是如果controller裡面變數名稱不一樣就炸了,而且這樣子寫比較不好瞭解到底是在幹嘛。
比較清楚一點的方法是自己丟變數給他
#view
<%= render partial: 'form', locals: { candidate: @candidate } %>
#也可以這樣就好
<%= render 'form', candidate: @candidate %>
#_form.html.erb可以拿掉@了
<%= form_for candidate do |f| %>
姓名: <%= f.text_field :name %><br/>
政黨: <%= f.text_field :party %><br/>
年紀: <%= f.text_field :age %><br/>
政見: <%= f.text_area :politics %><br/>
<%= f.submit %>
<% end %>
那如果是下面這種partial要拿掉@呢
<% @candidates.each do |candidate| %>
<li>
<%= link_to candidate.name, candidate_path(candidate) %>
<%= link_to
<%= link_to
</li>
<% end %>
那render就改成這樣,然後上面把@拿掉就好了
<%= render 'candidate', candidate: @candidates %>
但是這樣只是複製code貼上到另一個地方沒什麼意義,其實有辦法讓你連<% @candidates.each do |candidate| %>都不用寫
<%= render partial: 'candidate', collection: @candidates %>
#或者下面這樣,但因為精簡過頭我會不知道我在幹嘛,所以我不愛,記得第一次看到的時候整個滿頭問號
<%= render @candidates %>
<li>
<%= link_to candidate.name, candidate_path(candidate) %>
<%= link_to '編輯', edit_candidate_path(candidate) %>
<%= link_to '刪除', candidate_path(candidate), method: :delete, data: {confirm: '刪除嗎??'} %>
</li>
view helper
所有的helper其實都會被載入,那如果不同helper裡面有同樣名稱的method會發生什麼事?
雖然大家都會被載進來,但是畢竟載入還是有先後順序的,而順序是照英文字母的排列a..z,所以放在檔名開頭字母順序比較後面的那個method會被執行
自訂action member / collection
有member和collection兩種方法,差別是前者會幫你帶id進來,後者不會
Rails.application.routes.draw do
root 'welcome#index'
resources :candidates do
collection do
post :vote1
end
member do
post :vote2
end
end
end
vote1_candidates POST /candidates/vote1(.:format) candidates
vote2_candidate POST /candidates/:id/vote2(.:format) candidates
關聯
下面兩種寫法都可以設好Foreign Key,但我偶爾references會忘記加s=.=
另關聯其實是在model裡面做的,並不是在表格裡面做
#這兩個是一樣的
rails g model VoteLog ip_address candidate_id:integer
rails g model VoteLog ip_address candidate:references
如果是用下面那種寫法的話會幫你加以下的東西
class VoteLog < ApplicationRecord
belongs_to :candidate
end
has_many
慣例上has_many後面會用複數,但其實用單數也可以正常跑(但是打一串完全無關的字是不行的),因為他會去找model名字或是類別,只是就會用動,還是照慣例走吧
has_many :vote_logs
has_many :vote_log
counter cache
http://railsbook.tw/chapters/14-crud-part-2.html#step14-counter-cache
一般來說想要找出每一個candidate的vote_log的時候會需要去一筆筆的sql count查詢,像下面這樣
Candidate Load (0.2ms) SELECT "candidates".* FROM "candidates"
(0.3ms) SELECT COUNT(*) FROM "vote_logs" WHERE "vote_logs"."candidate_id" = ? [["candidate_id", 5]]
(0.3ms) SELECT COUNT(*) FROM "vote_logs" WHERE "vote_logs"."candidate_id" = ? [["candidate_id", 6]]
(0.7ms) SELECT COUNT(*) FROM "vote_logs" WHERE "vote_logs"."candidate_id" = ? [["candidate_id", 7]]
(0.1ms) SELECT COUNT(*) FROM "vote_logs" WHERE "vote_logs"."candidate_id" = ? [["candidate_id", 8]]
效能可能會比較不好,這時候可以用counter cache
先在Candadite新增一個欄位叫vote_logs_count
class AddCounter < ActiveRecord::Migration[5.0]
def change
add_column :candidates, :vote_logs_count, :integer, default: 0
end
end
rails db:migrate之後在model裡面改成下面這樣
class VoteLog < ApplicationRecord
belongs_to :candidate, counter_cache: true
end
然後在view裡面把count改成size,重新整理會發現sql變成只需要下面這樣
Candidate Load (0.5ms) SELECT "candidates".* FROM "candidates"
reset_counter
但會發現之前的票數不見了?!
可以直接進rails console去用,也可以寫個rake來執行
namespace :db do
desc "重置counter cache"
task :reset_counter => :environment do
Candidate.all.each do |candidate|
Candidate.reset_counters(candidate.id, :vote_logs)
end
end
end
執行之後票數就會正常了。
Written with StackEdit.