[v] Voting mechanism 投票機制

假設我們今天有一個網站,上面有 pins, 有 users, users 可以對 pins 投票,但每個 user 只能對每個 pin 投一個 upvote, 並且可以取消 upvote, 這個機制要怎麼設計呢?

首先,我們需要一個 Vote model, 而它是做為 join model 來連結 users 和 pins, 或者說,紀錄哪一位 user 投過哪一個 pin.

$ rails g model vote user_id: integer pin_id: integer
$ rake db:migrate

接著,我們要來建立各個資料庫之間的關聯:

models/pin.rb

class Pin < ActiveRecord::Base

  has_many :votes, dependent: :destroy
  has_many :upvoted_users, through: :votes, source: :user

  ...

end

models/user.rb

class User < ActiveRecord::Base

  has_many :votes, dependent: :destroy
  has_many :upvoted_pins, through: :votes, source: :pin

  ...

end

model/vote.rb

class Vote < ActiveRecord::Base

  belongs_to :user
  belongs_to :pin
  validates_uniqueness_of :pin_id, scope: :user_id

end

最後,我們在 controller 裡面寫上 actions.

controllers/ideas_controller.rb

def upvote
  @pin = Pin.find(params[:id])
  @pin.votes.create(user_id: current_user.id)
  
  if @pin.save
    flash[:notice] =  "Thank you for upvoting!"
    redirect_to :back
  else 
    flash[:notice] =  "You have already upvoted this!"
    redirect_to :back
  end
end

def cancel_upvote
  @pin = Pin.find(params[:id])
  @vote = Vote.find_by(user_id: current_user.id, pin_id: @pin.id)

  if @vote
    @vote.destroy
    flash[:notice] = "You've cancelled your upvote."
    redirect_to :back
  else
    flash[:warning] = "You have no upvotes to cancel."
    redirect_to :back
  end
end

這樣,我們就完成了這個投票機制。

也可以用同樣的概念讓每一個 user 可以對每一個 pin 投下一個 downvote, 並且也可以取消 downvote.

http://stackoverflow.com/questions/24596651/rails-allow-users-to-upvote-only-once-a-pin

[v] #005 Session 與 Cookie 的差別

學 Rails 半年了,半年前剛接觸這些主題時,只能透過 google 到的生冷名詞解釋來理解;半年後,許多踩雷的痛轉換成一點點經驗值,而能夠在每篇的最後寫上個人小小的心得,做為學習成果的驗收。

這是第 005 篇

什麼是 session 和 cookies?

為什麼要用 session 跟 cookies?

http://wiki.jikexueyuan.com/project/node-lessons/cookie-session.html
http://www.justinweiss.com/articles/how-rails-sessions-work/
http://www.theodinproject.com/ruby-on-rails/sessions-cookies-and-authentication

Rails 內如何操作 session?

Rails 操作 session 和 cookies 非常簡單,可以在 controller 與 view 中直接訪問,model 除外,極大地方便了開發。

Session

#存信息
session[:current_user_id] = user.id
#取信息
session[:current_user_id]
#刪除信息
session[:current_user_id] = nil
#清空信息
reset_session

Cookies
cookies 操作稍微有點多,因為關係到明文/加密,過期時長

1、默認為關閉瀏覽器,自動過期;明文存儲。
#存信息
cookies[:id] = "rubyer.me"
#取信息
cookies[:id] #輸出"rubyer.me"
#刪除信息
cookies.delete (:key) #與 session 不同
#清空信息(不能直接刪除,置過期即可)
<% cookies[:id] = {:expires => 2.weeks.ago.utc} %>
 
2、指定2周後過期
cookies[:user_preference] = {
  :value => @current_user.preferences,
  :expires => 2.weeks.from_now.utc
}
 
3、永久存儲。實際為20年後過期,非永久
cookies.permanent[:user_preference] = @current_user.preferences
 
4、加密存儲,key 即為 config/initializers/secret_token.rb 中的 Application.config.secret_token
cookies.signed[:id] = "45"
#存儲結果類似
cookies[:id] #返回「BAhJIgc0NQY6BkVU--a07249e5ce4374f62b7af628c70c679caa11c10b」
#讀取值
cookies.signed[:id] #返回"45"
 
5、加密並永久存儲
cookies.permanent.signed[:id] = "45"
cookies.signed[:id]

個人心得

雖然自己仍然是個 Rails 偽新手,但漸漸看得出一些學習上的問題點。網路上很多 Rails 初學者寫關於 session 和 cookies 的文章,參考的其實可能是其它初學者的文章,但由於還沒有能力判斷內容對錯,所以可能一開始的觀念就錯了。或許可以考慮直接看官方文件,或是參考有口碑的技術網站,比如 MDN, 再不然至少也還有知名的 Rails 大神寫的技術文可以參考。不能否認有那種有口碑但是錯誤不少的技術網站(比如W3School), 只能同一個主題多方參考了。

另外,名詞定義或版本上的混淆也是個問題。名詞定義來說,因為初學者懂的還太少,比如 Rails 的 session 是 cookie-based session, 必須和 cookie 一起使用,如果初學者看了一篇文章,說 session 其實可以單獨存在及使用,就可能搞錯 Rails session 的觀念。版本的部分,比如說 Rails 3 和 Rails 4 在 CookieStore 預設的做法有些微不同,而初學者未必會注意到不同版本有可能有巨大差異,如果查閱文件或文章時沒注意到版本號,甚至參考到同樣是初學者的文章,而對方同樣不知道版本號的影響,就有可能學到過時的、甚至被淘汰的觀念或實做方法。

參考資料

http://wiki.jikexueyuan.com/project/node-lessons/cookie-session.html

[v] #004 before_action

before_action 是什麼?

Filter 的一種, 定義執行 action 前要跑什麼 method. 另兩種常見的 filters 則是 after_action 以及 around_action.

為什麼要用 before_action?

一方面可以將每個 action 中最前面的重複代碼抽出,寫成一個 method, 用 before_action 執行。
另一方面,因為身分驗證往往是做在 action 前,所以也常被用來做身分驗證。

如何使用 before_action?

class ProductsController < ApplicationController
  before_action :authenticate_user!, only: [:create, :destroy]

  def index
  end
  
  def new
  end
  
  def create
  end
  
  def destroy
  end

  private

  def authenticate_user!
  end

end

除了 :only, 也可以搭配 :except, 比如:

before_action :authenticate_user!, except: [:index, :new]