Rails 學(xué)習(xí)雜記一

系統(tǒng)學(xué)習(xí) rails

Github項(xiàng)目地址

MVC

  • Controller 應(yīng)用邏輯
  • Model 數(shù)據(jù)
  • View 用戶界面
  • Model -> Controller -> View

目錄

  • app/ => assets 資源, controller 模型, helpers 主要服務(wù)于 views, mailers 郵件, models 數(shù)據(jù)庫(kù)層面的東西, view 視圖
  • bin/ => 用于啟動(dòng)應(yīng)用的 rails 腳本西壮,以及用于安裝、更新叫惊、部署或運(yùn)行應(yīng)用的其他腳本款青。
  • config/ => 配置應(yīng)用的路由、初始化霍狰、數(shù)據(jù)庫(kù)等
  • db/ => 當(dāng)前數(shù)據(jù)庫(kù)的模式抡草,以及數(shù)據(jù)庫(kù)遷移文件
  • log/ => 應(yīng)用請(qǐng)求日志
  • ...

Controller

  • rails 里面所有的 controller 都繼承 ApplicationController
  • ApplicationController 繼承 ActionController
  • welcome_controller.rb里面的 index 會(huì)默認(rèn)查找 views/welcome/index.html.erb

安裝 bootstrap

Model

  • 數(shù)據(jù)映射 Mapping ==> Database <-> Interface Virtual Data <-> Interface 可以是表, 也可以是虛擬數(shù)據(jù)
  • Ruby 里面是 ActiveRecord ==> ORM
  • Rails 對(duì)關(guān)系型數(shù)據(jù)庫(kù)的支持 SQlite, MySQL, PostgreSQL, Oracle, etc...

創(chuàng)建 User Model

rails g model user
  • 模型的命名使用 單數(shù)的 形式 user, 數(shù)據(jù)庫(kù)在映射的數(shù)據(jù)庫(kù)表映射為復(fù)數(shù)形式 CreateUsers
  • controller 的命名 復(fù)數(shù)的 形式
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :username
      t.string :password
      # 自動(dòng)維護(hù) 時(shí)間戳 創(chuàng)建 & 更新
      t.timestamps
    end
  end
end

bin/rails db:migrate

rails c

  • 打開一個(gè)當(dāng)前的控制臺(tái)
# User

# 創(chuàng)建一條 user
User.create username: 'yym', password: 123456
# 查詢所有
User.all
# 查詢第一條
User.first

# 更新
user = User.first
user.username = 'yym_2'
user.save

# 刪除
user.destroy

routes

users
method | path | action

列表

  • get /users users#index 輸出當(dāng)前資源的列表 ==> users
  • post /users users#create 創(chuàng)建一個(gè)資源 ==> users

表單相關(guān)

  • get /users/new users#new 和表單相關(guān), 提供用戶輸入 ==> new_user
  • get /users/:id/edit users#edit和表單相關(guān), 編輯一個(gè)資源 ==> edit_user

單一

  • get /users/:id users#show 獲取一個(gè)資源 ==> user
  • patch /users/:id users#update 更新一個(gè)資源 ==> user
  • put /users/:id users#update 更新一個(gè)資源 ==> user
  • delete /users/:id users#destroy 刪除一個(gè)資源 ==> user

Session & Cookie

  1. 先了解 HTTP -> 短連接 無(wú)狀態(tài)

  2. http 無(wú)狀態(tài), 怎么保存狀態(tài)的呢? -> Cookies

    • http response 會(huì) Set-Cookies
    • 前端發(fā)送請(qǐng)求可以 cookie: 后端返回的值
  3. session 和 cookie 的區(qū)別?

    • Session是后端實(shí)現(xiàn) -> session 的 key 存儲(chǔ)基于 cookie, 也可以基于后端存儲(chǔ), 比如數(shù)據(jù)庫(kù)
    • Cookie是瀏覽器的實(shí)現(xiàn)
    • Session 支持存儲(chǔ)后端的數(shù)據(jù)結(jié)構(gòu), 比如對(duì)象; Cookie只支持字符串
    • session沒有大小限制; cookie 大小有限制
  4. session 操作

    • session 方法; session key; session 加密的
  5. cookie 操作

    • cookie 方法; cookie 有效期, 域名等信息

Controller Filter

  • before_action -> action 執(zhí)行前
  • around_action
  • after_action

我們希望 看到用戶列表, 需要登錄

before_action :auth_user

private 
def auth_user
   unless session[:user_id]
   flash[:notice] = '請(qǐng)登錄'
   redirect_to new_session_path
   end
end

Routes 詳解

規(guī)定了特定格式的 URL 請(qǐng)求到后端 Controlleraction 的分發(fā)規(guī)則

自上而下的路由

例子

# GET /users/2
# method url  controller#action
get '/users/:id', to: 'users#show'
# or
get '/users/:id' => 'users#show'


# 命名路由 => 通過 :as 選項(xiàng)饰及,我們可以為路由命名
# logout GET    /exit(.:format)  sessions#destroy
get 'exit', to: 'sessions#destroy', as: :logout

RESTful 資源路由

  • 瀏覽器使用特定的 HTTP 方法向 Rails 應(yīng)用請(qǐng)求頁(yè)面,例如 GET康震、POST燎含、PATCH、PUT 和 DELETE腿短。每個(gè) HTTP 方法對(duì)應(yīng)對(duì)資源的一種操作
  • 同一個(gè) url 可以擁有不同的請(qǐng)求方式
  • 定義資源的method ==> resources :names ==> resources :photos
    HTTP 方法 路徑 控制器#動(dòng)作 用途
    GET /photos/new photos#new 返回用于新建照片的 HTML 表單
    GET /photos/:id/edit photos#edit 返回用于修改照片的 HTML 表單
    GET /photos photos#index 顯示所有照片的列表
    POST /photos photos#create 新建照片
    GET /photos/:id photos#show 顯示指定照片
    PATCH/PUT /photos/:id photos#update 更新指定照片
    DELETE /photos/:id photos#destroy 刪除指定照片
# 同時(shí)定義多個(gè)資源
resources :photos, :books, :videos

# ==> 

resources :photos
resources :books
resources :videos

# 單數(shù)資源
# 有時(shí)我們希望不使用 ID 就能查找資源屏箍。例如,讓 /profile 總是顯示當(dāng)前登錄用戶的個(gè)人信息
get 'profile', to: 'users#show'
# ==> 
get 'profile', to: :show, controller: 'users'

命名空間 namespace

namespace :admin do
   resources :articles
end
HTTP 方法 路徑 控制器#動(dòng)作 具名輔助方法
GET /admin/articles admin/articles#index admin_articles_path
GET /admin/articles/new admin/articles#new new_admin_article_path
POST /admin/articles admin/articles#create admin_articles_path
GET /admin/articles/:id admin/articles#show admin_article_path(:id)
GET /admin/articles/:id/edit admin/articles#edit edit_admin_article_path(:id)
PATCH/PUT /admin/articles/:id admin/articles#update admin_article_path(:id)
DELETE /admin/articles/:id admin/articles#destroy admin_article_path(:id)

如果我想要 /articles, 而不是 /admin/articals, 在 admin 文件夾下

# url 改變了, 但是 文件還是在 admin 對(duì)應(yīng)的文件夾下
# 可以設(shè)置多個(gè) resources
# module 模塊 admin 模塊下
scope module: 'admin' do
  resources :articles, :comments
end
# ==> 單個(gè)資源科以這樣聲明
resources :articles, module: 'admin'

但是如果我想要 /admin/articals, 但是不在 admin 文件夾下

# url 是 /admin/articles, 不在 admin 文件夾下
scope '/admin' do
  resources :articles, :comments
end

# 單個(gè)資源
resources :articles, path: '/admin/articles'

嵌套路由

  • url 語(yǔ)義更加明確
class User < ApplicationRecord
  has_many :blogs
end
 
class Blog < ApplicationRecord
  belongs_to :user
end
# 嵌套資源的層級(jí)不應(yīng)超過 1 層答姥。
resources :users do
  resources :blogs
end
  • 排除不需要的 action 和 請(qǐng)求方式
resources :users, only: [:index, :destroy]

集合路由(collection route)和成員路由(member route)

resources :users do
   # 成員路由, 一般 帶 id 的
   # post /users/:id/status => users#status
   member do
      post :status
   end

   # 集合路由, 不帶 id 的
   # get /users/online => users#online
   collection do
      get :online
   end
end

# 如果只需要定義一條方法 => :on 選項(xiàng)
resources :users do
   post :status, on: :member
   get :online, on: :collection
end

非資源式路由
除了資源路由之外铣除,對(duì)于把任意 URL 地址映射到控制器動(dòng)作的路由,Rails 也提供了強(qiáng)大的支持鹦付。和資源路由自動(dòng)生成一系列路由不同尚粘,這時(shí)我們需要分別聲明各個(gè)路由

# () 里面的內(nèi)容為可選參數(shù)
# /photos/1  or /photos  photos#display
# 綁定參數(shù)
get 'photos(/:id)', to: :display

# 動(dòng)態(tài)片段
# /photos/1/2  params[:id] = 1  params[:user_id] = 2
get 'photos/:id/:user_id', to: 'photos#show'

# 靜態(tài)片段
# /photos/1/with_user/2
get 'photos/:id/with_user/:user_id', to: 'photos#show'

# 查詢字符串
# /photos/1?user_id=2  -> id: 1 user_id: 2
get 'photos/:id', to: 'photos#show'

# 定義默認(rèn)值
# /photos/12 -> show action; params[:format] = 'jpg'
get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }

# http 方法約束
match 'photos', to: 'photos#show', via: [:get, :post]
match 'photos', to: 'photos#show', via: :all

# 片段約束
# /photos/A12345
get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ }
# ->
get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/

# 重定向
get '/stories', to: redirect('/articles')
get '/stories/:name', to: redirect('/articles/%{name}')

自定義資源路由

# :controller 選項(xiàng)用于顯式指定資源使用的控制器
resources :photos, controller: 'images'
# -> get /photos iamges#index

# 命名空間中額控制器目錄表示法
resources :user_permissions, controller: 'admin/user_permissions'

# 覆蓋具名路由輔助方法
# GET   /photos photos#index    as -> images_path
resources :photos, as: 'images'

創(chuàng)建帶有命名空間的 controller

bin/rails g controller admin::users

路由創(chuàng)建命名空間 & 添加 search

namespace :admin do
   # admin 下跟路由
   root 'users#index'
   # users 資源路由
   resources :users do
      # 創(chuàng)建 search
      collection do
         get :search
      end
   end
end

View

<%  %> # => 沒有輸出, 用于循環(huán) 遍歷 賦值等操作
<%=  %> # => 輸出內(nèi)容
  • index.html.erb index: action, html: 文件類型, erb: 解釋引擎

Render 作用

  • 生成 HTTP response
  • 渲染和解釋 子視圖(sub-view) -> 處理視圖

render in controller

  • 一個(gè) action 只能執(zhí)行一次 render , 否則會(huì)報(bào)錯(cuò) DoubleRenderError
# 避免多次 render
def index
   @blogs = Blog.all
   if @blogs
      render 'new'
   end

   render 'edit'
end
def search
   @users = User.all

   # 渲染到 index 動(dòng)作 頁(yè)面 
   render action: :index
   # 渲染其他控制器視圖
   render "products/show"
end


def search
   # ... render others
   render plain: "OK" # 純文本
   # html  html_safe 防止轉(zhuǎn)義
   render html: "<strong>Not Found</strong>".html_safe, status: 200
   # 渲染 json
   render json: @product, status: 404
   render json: {
      status: 'ok',
      msg: 'success'
   }
   # 渲染 xml
   render xml: @product, status: :no_content
   # 渲染普通 js
   render js: "alert('Hello Rails');"
   # 渲染文件
   render file: "/path/to/rails/app/views/books/edit.html.erb"
end
  • render 參數(shù)
# :layout -> 視圖使用 
# :location -> 用于設(shè)置 HTTP Location 首部
# :content_type
render file: filename, content_type: 'application/pdf'
# :status
render status: 500
render status: :forbidden

redirect_to 重定向

redirect_to user_path, status: 200
# 可以調(diào)試 api http 請(qǐng)求
curl -i http://localhostt:3000

form helper

  • form_tag => form_tag 方法會(huì)創(chuàng)建 <form> 標(biāo)簽,在提交表單時(shí)會(huì)向當(dāng)前頁(yè)面發(fā)起 POST 請(qǐng)求
  • form_for 表單元素
    • 生成一個(gè) HTML from , 用于模型或者 resource相關(guān)操作
<%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
  <%= f.text_field :title %>
  <%= f.text_area :body, size: "60x12" %>
  <%= f.submit "Create" %>
<% end %>
  • 實(shí)際需要修改的對(duì)象是 @article敲长。
  • form_for 輔助方法的選項(xiàng)是一個(gè)散列郎嫁,其中 :url 鍵對(duì)應(yīng)的值是路由選項(xiàng),:html 鍵對(duì)應(yīng)的值是 HTML 選項(xiàng)祈噪,這兩個(gè)選項(xiàng)本身也是散列泽铛。還可以提供:namespace 選項(xiàng)來(lái)確保表單元素具有唯一的 ID 屬性,自動(dòng)生成的 ID 會(huì)以 :namespace 選項(xiàng)的值和下劃線作為前綴辑鲤。
  • form_for 輔助方法會(huì)產(chǎn)出一個(gè)表單生成器對(duì)象盔腔,即變量 f。
  • 用于生成表單控件的輔助方法都在表單生成器對(duì)象 f 上調(diào)用月褥。

CSRF Cross-site Resouces Forgery

原理: 通過在頁(yè)面中包含惡意代碼或鏈接弛随,訪問已驗(yàn)證用戶才能訪問的 Web 應(yīng)用。如果該 Web 應(yīng)用的會(huì)話未超時(shí)宁赤,攻擊者就能執(zhí)行未經(jīng)授權(quán)的操作

  1. 示例1
    • Bob 在訪問留言板時(shí)瀏覽了一篇黑客發(fā)布的帖子舀透,其中有一個(gè)精心設(shè)計(jì)的 HTML 圖像元素。這個(gè)元素實(shí)際指向的是 Bob 的項(xiàng)目管理應(yīng)用中的某個(gè)操作决左,而不是真正的圖像文件:<img src="http://www.webapp.com/project/1/destroy">愕够。
    • Bob 在 www.webapp.com 上的會(huì)話仍然是活動(dòng)的,因?yàn)閹追昼娗八L問這個(gè)應(yīng)用后沒有退出佛猛。
    • 當(dāng) Bob 瀏覽這篇帖子時(shí)惑芭,瀏覽器發(fā)現(xiàn)了這個(gè)圖像標(biāo)簽,于是嘗試從 www.webapp.com 中加載圖像继找。如前文所述强衡,瀏覽器在發(fā)送請(qǐng)求時(shí)包含 cookie,其中就有有效的會(huì)話 ID
  2. 允許用戶自定義 超鏈接
// 注入 js
<a  onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;">To the harmless survey</a>

<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
  1. rails 應(yīng)對(duì)措施
    • 使用正確的 http 請(qǐng)求
    • protect_from_forgery with: :exception
    • 對(duì)用戶輸入做限制

Controller

  • actionpack gem
  • ActionController::Base 命名空間下的

  1. 使用

    • app/controllers 目錄
    • 命名規(guī)則 names_controller.rb
    • 支持命名空間, 以module組織
  2. Instance Methods in Controller

    • params
      • 獲取http請(qǐng)求中 get post的 參數(shù)
      • 可以使用Symbol 和 String的方式訪問, 比如params[:user] params['user']
    • session & cookies
    • render -> 上面有講
    • redirect_to
    • send_data & send_file -> 以數(shù)據(jù)流的方式發(fā)送數(shù)據(jù)
      • send_file 方法很方便,只要提供磁盤中文件的名稱漩勤,就會(huì)用數(shù)據(jù)流發(fā)送文件內(nèi)容
      def download
         send_file '/file/to/download.pdf'
      end
      def download
         send_data image.data, type: image.content_type
      end
      
    • request
      • request.get? request.headers request.query_string etc...
    • response
      response.location response.body etc...
  3. Class Methods in Controller

    • Filters
      • before_action after_action around_action

Exception 異常

  1. 捕獲錯(cuò)誤后如果想做更詳盡的處理感挥,可以使用 rescue_from, rescue_from 可以處理整個(gè)控制器及其子類中的某種(或多種)異常
class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

  private

    def record_not_found
      render plain: "404 Not Found", status: 404
    end
end
  1. 不過只要你能捕獲異常,就可以做任何想做的處理越败。例如触幼,可以新建一個(gè)異常類,當(dāng)用戶無(wú)權(quán)查看頁(yè)面時(shí)拋出
class ApplicationController < ActionController::Base
  rescue_from User::NotAuthorized, with: :user_not_authorized
  private
    def user_not_authorized
      flash[:error] = "You don't have access to this section."
      redirect_back(fallback_location: root_path)
    end
end

class ClientsController < ApplicationController
  # 檢查是否授權(quán)用戶訪問客戶信息
  before_action :check_authorization

  # 注意究飞,這個(gè)動(dòng)作無(wú)需關(guān)心任何身份驗(yàn)證操作
  def edit
    @client = Client.find(params[:id])
  end

  private

    # 如果用戶沒有授權(quán)日麸,拋出異常
    def check_authorization
      raise User::NotAuthorized unless current_user.admin?
    end
end

Model 了解

  1. 約束

    • 表名(table_name)
    # user model 對(duì)應(yīng)數(shù)據(jù)庫(kù)中的 users 表
    class user < ActiveRecord::Base
    end
    
    • columns
      • 對(duì)應(yīng)我的 users 表, column -> id username password created_at updated_at
  2. 覆蓋表名

    class Product < ApplicationRecord
       self.primary_key = "product_id" # 指定表的主鍵
       self.table_name = "my_products" # default -> products
    end
    
  3. CURD

    • new 方法創(chuàng)建一個(gè)新對(duì)象决采,create 方法創(chuàng)建新對(duì)象,并將其存入數(shù)據(jù)庫(kù)
    class user < ActiveRecord::Base
    end
    
    # create
    # 1
    user = User.new username = "David", password = "123456"
    user.save
    # 2
    User.create username = "David", password = "123456"
    
    # Reader
    users = User.all # 返回所有用戶組成的集合
    user = User.first # 返回第一個(gè)用戶
    david = User.find_by(name: 'David') # 返回第一個(gè)名為 David 的用戶
    # 查找所有名為 David,職業(yè)為 Code Artists 的用戶髓帽,而且按照 created_at 反向排列
    users = User.where(name: 'David', occupation: 'Code Artist').order(created_at: :desc)
    
    # Update
    # 1
    user = User.find_by(name: 'David')
    user.name = 'Dave'
    user.save
    # 2
    user = User.find_by(name: 'David')
    user.update(name: 'Dave')
    # 3
    User.update_all "max_login_attempts = 3, must_change_password = 'true'"
    
    # Delete
    user = User.find_by(name: 'David')
    user.destroy
    
  4. 表間關(guān)聯(lián)關(guān)系

  • has_many: 一對(duì)多, 多對(duì)多
  • has_one: 一對(duì)一
  • belongs_to: 一對(duì)一
  • has_and_belongs_to_many: 多對(duì)多
# 示例
class User < ActiveRecord::Base
   has_many :blogs
end
class Blog < ActiveRecord::Base
   belongs_to  :user
end

model table column key
User  users  id primary_key => User 模型 users 表 id 字段 主鍵
Blog blogs user_id foreign_key => Blog 模型 blogs表 user_id 字段 外鍵
  1. 多對(duì)多
  • has_and_belongs_to_many
    • 使用場(chǎng)景: 一片博客會(huì)有很多個(gè)標(biāo)簽, 一個(gè)標(biāo)簽?zāi)芷ヅ浜芏嗖┛?/li>
    • 示例: 創(chuàng)建三張表 blogs tags blogs_tags
class Blog < ApplicationRecord
  has_and_belongs_to_many :tags
end

class Tag < ApplicationRecord
  has_and_belongs_to_many :blogs
end

# 創(chuàng)建表
class CreateTags < ActiveRecord::Migration[6.0]
  def change
    create_table :tags do |t|
      t.string :title
      t.timestamps
    end
    create_table :blogs_tags do |t|
      t.integer :blog_id
      t.integer :tag_id
    end
  end
end

  • has_many
    • 使用場(chǎng)景: 我們需要訪問關(guān)聯(lián)關(guān)系表blogs_tags, 并且在關(guān)聯(lián)關(guān)系表blogs_tags 添加一些自定義字段, 回調(diào), 屬性檢查等
    • has_many :through
  1. CURD 深入
# 查找

# find vs find_by 區(qū)別
# 1. 傳參不同; 2: 錯(cuò)誤時(shí) find_by 返回 nil, find 拋出異常; 3. find 只允許傳入 id, find_by 可以傳入其他參數(shù)
find(id) # 直接傳入 id
find_by(id: 1) # 傳入 hash 值
find_by! # ! 錯(cuò)誤會(huì)拋出異常

# 指定 sql 語(yǔ)句 來(lái)查詢數(shù)據(jù)庫(kù)
find_by_sql # 總是返回對(duì)象的數(shù)組
User.find_by_sql "select * from users where id = 1"

ids # 獲得關(guān)聯(lián)的所有 ID
User.ids # [1, 10, 11, 12]


# where
# 在沒有操作之前. 只是封裝查詢對(duì)象
Blog.where(["id = ? and title = ?", params[:id], params[:title]]) # 自定義數(shù)組 ? 替換
Blog.where(id: 3, title: 'test') # hash 的模式

# 什么是: n+1 查詢 => 例如博客列表頁(yè)面, 我們一次請(qǐng)求 列表次數(shù)的 sql, 但每個(gè)列表都有 標(biāo)簽. 每行列表都要請(qǐng)求一次 標(biāo)簽 sql, 所以是 n + 1 次 查詢
includes(:tags, :user) # 自動(dòng)把關(guān)聯(lián)關(guān)系一次查出來(lái)

# 更新

# Blog 模型

# 1
b = Blog.first
b.title = 'chenges'
b.save

# 2 update_attributes  賦值 + save
b.update_attributes title: 'test', content: 'content'

# 3. update_attribute 賦值 + save
b.update_attribute :title, '我是標(biāo)題'

changed? # 返回 boolean
changed_attributes # 返回上次記錄

# ! 方法
# 會(huì)拋出異常
save!

create!

update_attributes!
# Exception
ActiveRecord::RecordNotFound # 未發(fā)現(xiàn)記錄
ActiveRecord::UnknownAttributeError  # 未知屬性
ActiveRecord::RecordInvalid # 驗(yàn)證未通過
ActiveRecord::Rollback # 
etc...
  1. Model 自定義屬性
class Blog < ApplicationRecord
  def tags_string= one_tags
    one_tags.split(',').each do |tag|
      one_tag = Tag.find_by(title: tag)
      one_tag = Tag.new(title: tag) unless one_tag
      # 博客就在當(dāng)前范圍
      self.tags << one_tag
    end
  end

  def content= one_content
   write_attributes :content, one_content * 2
  end

  # 轉(zhuǎn)化拼音
  def title_pinyib 
   Pingyin.t self.title
  end
end
  1. 事務(wù) transaction
  • 事務(wù)文章
    事務(wù)用來(lái)確保多條 SQL 語(yǔ)句要么全部執(zhí)行成功渊啰、要么不執(zhí)行胳蛮。事務(wù)可以幫助開發(fā)者保證應(yīng)用中的數(shù)據(jù)一致性, 使用事務(wù)的場(chǎng)景是銀行轉(zhuǎn)賬麸粮,錢從一個(gè)賬戶轉(zhuǎn)移到另外一個(gè)賬戶。如果中間的某一步出錯(cuò)酬滤,那么整個(gè)過程應(yīng)該重置

  • 觸發(fā)事務(wù)回滾

    • 事務(wù)通過 rollback 過程把記錄的狀態(tài)進(jìn)行重置签餐。在 Rails 中,rollback 只會(huì)被一個(gè) exception 觸發(fā)盯串。這是非常關(guān)鍵的一點(diǎn)氯檐,很多事務(wù)塊中的代碼不會(huì)觸發(fā)異常,因此即使出錯(cuò)体捏,事務(wù)也不會(huì)回滾
  • 事務(wù)是基于database層級(jí)不是針對(duì)一個(gè)表

# 會(huì)觸發(fā) rollback
Blog.transaction do
   blog.save!
end

# 會(huì)一直觸發(fā) rollback
Blog.transaction do
   raise 'error'
end
  • 應(yīng)用場(chǎng)景: 項(xiàng)目博客和標(biāo)簽創(chuàng)建

數(shù)據(jù)驗(yàn)證

class Person < ApplicationRecord
  validates :name, presence: true
  valiedates_prensence_of :name
end
  • valid? & invalid? -> valid: 有效的; invalid: 無(wú)效的
class Person < ApplicationRecord
  validates :name, presence: true
end
 
# valid? 方法會(huì)觸發(fā)數(shù)據(jù)驗(yàn)證冠摄,如果對(duì)象上沒有錯(cuò)誤,返回 true几缭,否則返回 false
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
  • 處理驗(yàn)證錯(cuò)誤

    • ActiveModel::Errors
    • errors -> 鍵是每個(gè)屬性的名稱河泳,值是一個(gè)數(shù)組,包含錯(cuò)誤消息字符串奏司。
    class Person < ApplicationRecord
       validates :name, presence: true, length: { minimum: 3 }
    end
    
    person = Person.new
    person.valid? # => false
    person.errors.messages
    # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]}
    
    person = Person.new(name: "John Doe")
    person.valid? # => true
    person.errors.messages # => {}
    
    • errors[] -> 若想檢查對(duì)象的某個(gè)屬性是否有效乔询,可以使用 errors[:attribute]樟插。errors[:attribute] 中包含與 :attribute 有關(guān)的所有錯(cuò)誤韵洋。如果某個(gè)屬性沒有錯(cuò)誤,就會(huì)返回空數(shù)組
    • errors.add -> 手動(dòng)添加某屬性的錯(cuò)誤消息黄锤,它的參數(shù)是屬性和錯(cuò)誤消息
    • errors.details
  • 自定義validate

    • 自定義驗(yàn)證類: 自定義的驗(yàn)證類繼承自 ActiveModel::Validator搪缨,必須實(shí)現(xiàn) validate 方法,其參數(shù)是要驗(yàn)證的記錄鸵熟,然后驗(yàn)證這個(gè)記錄是否有效副编。自定義的驗(yàn)證類通過 validates_with 方法調(diào)用
    class MyValidator < ActiveModel::Validator
       def validate(record)
          unless record.name.starts_with? 'X'
             record.errors[:name] << 'Need a name starting with X please!'
          end
       end
    end
    
    class Person
       include ActiveModel::Validations
       validates_with MyValidator
    end
    
  • 觸發(fā)時(shí)間

    • create save update
# on 啥時(shí)候
class Person < ApplicationRecord
  # 更新時(shí)允許電子郵件地址重復(fù)
  validates :email, uniqueness: true, on: :create
 
  # 創(chuàng)建記錄時(shí)允許年齡不是數(shù)字
  validates :age, numericality: true, on: :update
 
  # 默認(rèn)行為(創(chuàng)建和更新時(shí)都驗(yàn)證)
  validates :name, presence: true
end

scopes 作用域

  • 作用域
  • 作用域允許我們把常用查詢定義為方法,然后通過在關(guān)聯(lián)對(duì)象或模型上調(diào)用方法來(lái)引用這些查詢
  • 指定默認(rèn)的查詢參數(shù), 限定查詢范圍
# 定義作用域和通過定義類方法來(lái)定義作用域效果完全相同
class User < ApplicationRecord
   scope :published, -> { where(published: true) }

   # 等同于
   def self.published
     where(published: true)
   end
end

# 調(diào)用
User.published.first
User.published.new
# 傳入?yún)?shù)
class Article < ApplicationRecord
  # 創(chuàng)建之前
  scope :created_before, ->(time) { where("created_at < ?", time) }
end

Article.created_before(Time.zone.now)

關(guān)聯(lián)關(guān)系參數(shù)

  • proc
class User < ActiveRecord::Base
   has_many :blogs

   # -> 代碼塊, 類的名稱: Blog 類似 scope
   has_many :public_blogs, -> { where(is_public: true) }, class_name: :Blog

   # 查出用戶最新的一篇博客 has_one 查一個(gè)
   has_one :latest_blog, -> { order("id desc") }, class_name: :Blog

   # 自身關(guān)聯(lián) 用戶里面有管理者 和 普通員工
   has_many :普通人, class_name: :User, foreign_key: 'manager_id'
   belongs_to :管理者, class_name: :User
end

# 自身關(guān)聯(lián)的使用
@user.普通人 和 @user.管理者

# 約束復(fù)習(xí)
blogs -> Blog # blogs 對(duì)應(yīng) Blog Model 
User -> user_id
User primary_key -> id
Blog foreign_key -> user_id
# 指定關(guān)聯(lián)參數(shù)
class User < ActiveRecord::Base
   # 模型名稱 class_name 指定, 主鍵: primary_key 指定, 外鍵: foreign_key
   has_many :blogs, class_name: :Blog, primary_key: :id, foreign_key: :user_id
end

# :dependent
class User < ActiveRecord::Base
   # :dependent 選項(xiàng)控制屬主銷毀后怎么處理關(guān)聯(lián)的對(duì)象
   # :destroy 銷毀關(guān)聯(lián)的對(duì)象
   has_many :blogs, dependent: :destroy
end

user = User.first
user.destroy # blogs 也會(huì)被銷毀

Migration

  • 數(shù)據(jù)庫(kù)模式一開始并不包含任何內(nèi)容流强,之后通過一個(gè)個(gè)遷移來(lái)添加或刪除數(shù)據(jù)表痹届、字段和記錄呻待。 Active Record 知道如何沿著時(shí)間線更新數(shù)據(jù)庫(kù)模式,使其從任何歷史版本更新為最新版本队腐。Active Record 還會(huì)更新 db/schema.rb 文件蚕捉,以匹配最新的數(shù)據(jù)庫(kù)結(jié)構(gòu)
  1. 作用
  • 使用 Ruby DSL 管理數(shù)據(jù)庫(kù)的設(shè)計(jì)模式,因此不必手動(dòng)編寫 SQL
  • 通用 RDB 模式管理, 方便在不同數(shù)據(jù)庫(kù)之間使用
  • 支持版本管理和團(tuán)隊(duì)協(xié)作
  • 支持?jǐn)?shù)據(jù)庫(kù) rollback
  1. 使用
bin/rails g model
# 已存在的 model, 添加或者修改一些字段, 不需要生成 model, 只需要生成一個(gè)單獨(dú)的移植文件
bin/rails g migration

bin/rails db:migrate
# 示例: 為用戶添加一個(gè) style 字段
# bin/rails g migration add_users_style_column
class AddUsersStyleColumn < ActiveRecord::Migration[6.0]
  def change
    # 添加字段 :針對(duì) users 表, :字段名, :字段類型 
    add_column :users, :style, :string
  end
end
  1. 運(yùn)行遷移
  • Rails 提供了一套用于運(yùn)行遷移的 bin/rails 任務(wù)
# 常用 調(diào)用所有未運(yùn)行的遷移中的 change up 方法
bin/rails db:migrate (VERSION=20080906120000) # version 可選參數(shù)

# 查看遷移的狀態(tài)
bin/rails db:migrate:status
# up     20211019121601  Create tags
# down   20211021070702  Add users style column

# 回滾 : 通過撤銷 change 方法或調(diào)用 down 方法來(lái)回滾最后一個(gè)遷移
bin/rails db:rollback
# 會(huì)撤銷最后三個(gè)遷移
bin/rails db:rollback STEP=3
  • up & down 方法
    • up 方法用于描述對(duì)數(shù)據(jù)庫(kù)模式所做的改變柴淘,down 方法用于撤銷 up 方法所做的改變
class AddUsersStyleColumn < ActiveRecord::Migration[6.0]
   # migrate 運(yùn)行
  def up
    add_column :users, :style, :string
  end
  # rollback 運(yùn)行
  def dowm
   remove_column :users, :style, :string
  end
end

class AddUsersStyleColumn < ActiveRecord::Migration[6.0]
  def change
    # 添加字段
    add_column :users, :style, :string
    # 刪除字段
    remove_column :table_name, :column_name
    # 修改字段
    change_column :table_name, :column_name, :column_type, :column_options
    # 重命名
    rename_column :table_name, :old_column_name, :new_column_name
  end
end
  • 永遠(yuǎn)不要修改已經(jīng)提交的 migration
    • 創(chuàng)建新的 migration 修復(fù) 之前的錯(cuò)誤

Callback

什么是 callback -> 回調(diào)是在對(duì)象生命周期的某些時(shí)刻被調(diào)用的方法迫淹。通過回調(diào),我們可以編寫在創(chuàng)建为严、保存敛熬、更新、刪除第股、驗(yàn)證或從數(shù)據(jù)庫(kù)中加載 Active Record 對(duì)象時(shí)執(zhí)行的代碼

  1. 回調(diào)觸發(fā)分類
  • 創(chuàng)建對(duì)象
    • before_validation -> 數(shù)據(jù)驗(yàn)證前
    • after_validation -> 數(shù)據(jù)驗(yàn)證后
    • before_save -> 保存前
    • around_save
    • before_create -> 創(chuàng)建才會(huì)執(zhí)行
    • around_create
    • after_create
    • after_save
    • after_commit/after_rollback
  • 更新對(duì)象
    • before_validation
    • after_validation
    • before_save
    • around_save
    • before_update
    • around_update
    • after_update
    • after_save
    • after_commit/after_rollback
  • 刪除對(duì)象
    • before_destroy
    • around_destroy
    • after_destroy
    • after_commit/after_rollback
  • 查詢對(duì)象
    • after_initialize
    • after_find
  • 事務(wù)回調(diào) after_commit/after_rollback
    • 所有回調(diào)自動(dòng)放入事務(wù)中
    • 如果一個(gè) before_* callback返回值為false,name當(dāng)前事務(wù)就會(huì)回滾 (還未進(jìn)入數(shù)據(jù)庫(kù))
  • 無(wú)論按什么順序注冊(cè)回調(diào)应民,在創(chuàng)建和更新對(duì)象時(shí),after_save 回調(diào)總是在更明確的 after_create 和 after_update 回調(diào)之后被調(diào)用
class User < ApplicationRecord
  before_save do
    self.username = self.username.downcase
  end

  # or
  before_save :update_username

  private
  def update_username
    self.username = self.username.downcase
  end
end
  • 觸發(fā) callback 的方法
    • create
    • create!
    • decrement!
    • destroy
    • destroy!
    • destroy_all
    • increment!
    • save
    • save!
    • save(validate: false)
    • toggle!
    • update_attribute
    • update
    • update!
    • valid?
  • 跳過回調(diào)的操作
    • decrement
    • decrement_counter
    • delete
    • delete_all
    • increment
    • increment_counter
    • toggle
    • touch
    • update_column
    • update_columns
    • update_all
    • update_counters
# before_save & after_save
class User < ApplicationRecord
  # 是在保存之前調(diào)用, 直接賦值即可
  before_save do
    self.username = self.username.downcase
  end

  # 保存之后, 所以需要 save
  # 做一些和本模型無(wú)關(guān)的操作, 其他模型相關(guān)操作
  after_save do
    self.username = self.username.downcase
    # 會(huì)死循環(huán)
    self.save # update_column 代替
  end
end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末炸茧,一起剝皮案震驚了整個(gè)濱河市瑞妇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梭冠,老刑警劉巖辕狰,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異控漠,居然都是意外死亡蔓倍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門盐捷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)偶翅,“玉大人,你說(shuō)我怎么就攤上這事碉渡【鬯” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵滞诺,是天一觀的道長(zhǎng)形导。 經(jīng)常有香客問我,道長(zhǎng)习霹,這世上最難降的妖魔是什么朵耕? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮淋叶,結(jié)果婚禮上阎曹,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好处嫌,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布栅贴。 她就那樣靜靜地躺著,像睡著了一般熏迹。 火紅的嫁衣襯著肌膚如雪筹误。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天癣缅,我揣著相機(jī)與錄音厨剪,去河邊找鬼。 笑死友存,一個(gè)胖子當(dāng)著我的面吹牛祷膳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屡立,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼直晨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了膨俐?” 一聲冷哼從身側(cè)響起勇皇,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎焚刺,沒想到半個(gè)月后敛摘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乳愉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年兄淫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔓姚。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捕虽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坡脐,到底是詐尸還是另有隱情泄私,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布备闲,位于F島的核電站晌端,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏浅役。R本人自食惡果不足惜斩松,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一伶唯、第九天 我趴在偏房一處隱蔽的房頂上張望觉既。 院中可真熱鬧,春花似錦、人聲如沸瞪讼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)符欠。三九已至嫡霞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間希柿,已是汗流浹背诊沪。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曾撤,地道東北人端姚。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像挤悉,于是被迫代替她去往敵國(guó)和親渐裸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容