《Ruby on Rails教程》學習筆記


本文是我在閱讀 Ruby on Rails 教程簡體中文版時所做的摘錄,以及學習時尋找的補充知識。補充知識主要來自於 Ruby on Rails 實戰聖經

Asset Pipeline

在最新版 Rails 中,靜態文件可以放在三個標准文件夾中,而且各有各的用途:

  • app/assets:當前應用的資源文件;
  • lib/assets:開發團隊自己開發的代碼庫使用的資源文件;
  • vendor/assets:第三方代碼庫使用的資源文件;

*= require_tree .
會把 app/assets/stylesheets 文件夾中的所有 CSS 文件(包含子文件夾中的文件)都引入應用的 CSS 。

*= require_self
會把 application.css 這個文件中的 CSS 也加載進來。

(預處理器引擎)按照擴展名的順序從右向左處理

使用 Asset Pipeline,生產環境中應用所有的樣式都會集中到一個 CSS 文件中(application.css),所有 JavaScript 代碼都會集中到一個 JavaScript 文件中(application.js),而且還會壓縮這些文件,刪除不必要的空格,減小文件大小。

Active Record

關於回調

...“回調”(callback),在 Active Record 對象生命周期的特定時刻調用。現在,我們要使用的回調是 before_save,在用戶存入數據庫之前把電子郵件地址轉換成全小寫字母形式。

補充

其他的回調有:
savevalidbefore_validationvalidateafter_validationbefore_savebefore_createcreateafter_createafter_saveafter_commit

關於索引

add_index :users, :email, unique: true

上述代碼調用了 Rails 中的 add_index 方法,為 users 表中的 email 列建立索引。索引本身並不能保證唯一性,所以還要指定 unique: true

add_index :microposts, [:user_id, :created_at]

我們把 user_idcreated_at 放在一個數組中,告訴 Rails 我們要創建的是“多鍵索引”(multiple key index),因此 Active Record 會同時使用這兩個鍵。

關於順序

為了得到特定的順序,我們要在 default_scope 方法中指定 order 參數,按 created_at 列的值排序

default_scope -> { order(created_at: :desc) }

關於依賴屬性

dependent: :destroy 的作用是在用戶被刪除的時候,把這個用戶發布的微博也刪除。

補充

:dependent可以有三種不同的刪除方式,分別是::destroy:delete:nullify

關於自定義

has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent:   :destroy

belongs_to :follower, class_name: "User" 
belongs_to :followed, class_name: "User"

has_many :following, through: :active_relationships, source: :followed

補充

指定表名和主鍵:

class Category < ActiveRecord::Base
  self.table_name = "your_table_name"
  self.primary_key = "your_primary_key_name"
end

補充

書中多對多的關系自定義了很多名字,如果采用Rails默認命名,是下面的樣子:

class Event < ActiveRecord::Base
  has_many :event_groupships
  has_many :groups, :through => :event_groupships
end

class EventGroupship < ActiveRecord::Base
  belongs_to :event
  belongs_to :group 
end

class Group < ActiveRecord::Base
  has_many :event_groupships
  has_many :events, :through => :event_groupships
end

測試

關於固件

固件的作用是為測試數據庫提供示例數據

test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>

創建了一個有效用戶固件后,在測試中可以使用下面的方式獲取這個用戶:

user = users(:michael)

其中,users 對應固件文件 users.yml 的文件名,:michael 是代碼清單 8.19 中定義的用戶。

一個很好的測試用例

test "login with valid information" do
    get login_path
    post login_path, session: { email: @user.email, password: 'password' }
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
  end
end

在這段代碼中,我們使用 assert_redirected_to @user 檢查重定向的地址是否正確;使用 follow_redirect! 訪問重定向的目標地址。還確認頁面中有零個登錄鏈接,從而確認登錄鏈接消失了:

assert_select "a[href=?]", login_path, count: 0

count: 0 參數的目的是,告訴 assert_select,我們期望頁面中有零個匹配指定模式的鏈接。(代碼清單 5.25中使用的是 count: 2,指定必須有兩個匹配模式的鏈接。)

安全

密碼的實現

在模型中調用這個方法(has_secure_password)后,會自動添加如下功能:

  • 在數據庫中的 password_digest 列存儲安全的密碼哈希值;
    (通過下面遷移獲得:)

      add_column :users, :password_digest, :string
    
  • 獲得一對“虛擬屬性”,passwordpassword_confirmation,而且創建用戶對象時會執行存在性驗證和匹配驗證;

      User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar")
    
  • 獲得 authenticate 方法,如果密碼正確,返回對應的用戶對象,否則返回 false

      user = User.find_by(email: "mhartl@example.com")
      user.authenticate("not_the_right_password")
    

使用bcrypt驗證記憶令牌

BCrypt::Password.new(remember_digest).is_password?(remember_token)

關於健壯參數

private
  def user_params
    params.require(:user).permit(:name, :email, :password,
                                       :password_confirmation)
  end
@user = User.new(user_params)

必須只允許通過請求傳入可安全編輯的屬性。
admin 並不在允許使用的屬性列表中。這樣就可以避免用戶取得網站的管理權。

Session和Cookie

經過上述分析,我們計划按照下面的方式實現持久會話:

  1. 生成隨機字符串,當做記憶令牌;
  2. 把這個令牌存入瀏覽器的 cookie 中,並把過期時間設為未來的某個日期;
  3. 在數據庫中存儲令牌的摘要;
  4. 在瀏覽器的 cookie 中存儲加密后的用戶 ID;
  5. 如果 cookie 中有用戶的 ID,就用這個 ID 在數據庫中查找用戶,並且檢查 cookie 中的記憶令牌和數據庫中的哈希摘要是否匹配。

Cookie的用法:

cookies[:remember_token] = { value:   remember_token,
                         expires: 20.years.from_now.utc }

另一種簡寫:

cookies.permanent[:remember_token] = remember_token

加密Cookie:

cookies.signed[:user_id] = user.id

控制器

為了實現圖 9.6 中的轉向功能,我們要在用戶控制器中使用“事前過濾器”。事前過濾器通過 before_action 方法設定,指定在某個動作運行前調用一個方法。

默認情況下,事前過濾器會應用於控制器中的所有動作,所以在上述代碼中我們傳入了 :only 參數,指定只應用在 editupdate 動作上。

模板

<%= render @users %>

Rails 會把 @users 當作一個 User 對象列表,傳給 render 方法后,Rails 會自動遍歷這個列表,然后使用局部視圖 _user.html.erb 渲染每個對象。

關於表單

params 哈希中包含一個基於復選框狀態的值。如果勾選了復選框,params[:session][:remember_me] 的值是 '1',否則是 '0'。

代碼清單 9.2 和代碼清單 7.13 都使用了相同的 form_for(@user) 來構建表單,那么 Rails 是怎么知道創建新用戶要發送 POST 請求,而編輯用戶時要發送 PATCH 請求的呢?這個問題的答案是,通過 Active Record 提供的 new_record? 方法檢測用戶是新創建的還是已經存在於數據庫中

form_for(@user) 的作用是讓表單向 /users 發起 POST 請求。對會話來說,我們需要指明資源的名字以及相應的 URL:

form_for(:session, url: login_path)

補充

form_forform_tag的區別:

一種是對應到Model物件的新增、修改,我們會使用form_for這個Helper。它的好處在於透過傳入Model物件,可以在修改的時候自動幫你將預設值帶入。例如我們已經在Part1使用過的event表單:

<%= form_for @event do |f| %>
    <%= f.text_field :name %>
    <%= f.submit %>
<% end %>

另一種是就是沒有對應Model的表單,我們使用form_tag這個方法。例如:

<%= form_tag "/search" do %>
    <%= text_field_tag :keyword %>
    <%= submit_tag %>
<% end %>

form_tag有些類似,但是其中不需要傳Block變數f,其中的欄位Helper需要多加_tag結尾。不像form_for的欄位名稱一定要是Model的屬性之一,在form_tag之中的欄位名稱則完全不受限。

路由

resources :users do
    member do
      get :following, :followers
    end
  end

設定上述路由后,得到的 URL 地址類似 /users/1/following/users/1/followers 這種形式。

除此之外,我們還可以使用 collection 方法,但 URL 中就沒有用戶 ID 了。

resources :users do
  collection do
    get :tigers
  end
end

得到的 URL 是 /users/tigers

補充

另外還有這種使用方法:

resources :projects do
  resources :tasks
end

得到的URL如projects/123/tasksprojects/123/tasks/123

Ajax

只要把 form_for 改成 form_for…​, remote: true,Rails 就會自動使用 Ajax 處理表單。

補充

同理於超連結 link_to,按鈕 button_to 加上:remote => true參數也會變成 Ajax。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM