Skip to main content

ActiveRecord

在 rails 中的 model 層,把資料表的一筆資訊包裝成一個物件,並可在物件上增加額外的邏輯操作,讓資料存取更便利

  • 與資料庫的互動主要透過 ActiveRecord 提供的方法,這些方法包括查詢資料、新增、更新、刪除( CRUD )等操作
  • Object Relational Mapping(ORM) 框架,用在關聯型資料庫和應用程式之間建立、操作、處理資料,透過 ORM 可以更容易以物件的方式存取資料庫中的資料而不需直接撰寫 SQL

CRUD

關於 Rails 的 CRUD,指的是操作資料庫的四種基本操作, Create、Read、Update、Delete

Create

  • new : 不會寫進資料庫
  • create : 會寫進資料庫
  • create! : 嚴格模式

Read

  • first
  • last
  • find_by(id: 1) : 找不到時出現 nil
  • find : 找不到時出現 exception(異常)
  • find_each : 預設一次跑 1000 筆資料
    Product.find_each do |product|
    //...
    end
  • all
  • select : 只選取 name 欄位
    Product.select('name')
  • where : 找出所有 name 欄位是 ruby 的資料
    Product.where(name: 'ruby')
  • order : 依照 id 大小反向排序
    Product.order('id DESC')
    Product.order(id: :desc)
  • limit : 只取出五筆資料
    Product.limit(5)
  • count
  • sum
  • average
  • maximum
  • minimum

Update

  • save
  • update
  • update_attributes
  • update_all
  • increment
  • decrement
  • toggle

Delete

  • delete
    • 直接刪除
  • destroy
    • 刪除過程會有 callback
  • destroy_all(condition = nil)
    • 可以附加條件
    Product.destroy_all("price < 10")

rails 支援的關聯

has_one

  • 不是設定是一個類別方法
  • 表示 model 一對一的關係

如果有一個 User model,並且讓每個使用者只擁有一個檔案,可以使用 has_one 關聯,並且確保 profile 表有 user_id 的欄位,才能使用 belongs_to 聲明它屬於 User model

# models/user.rb
class User < ApplicationRecord
has_one :profile
end

# models/profile.rb
class Profile < ApplicationRecord
belongs_to :user
end

執行 has_one :profile,會動態產生好幾個方法:profile, profile=, build_profile, create_profile

belongs_to

  • 是一個類別方法
  • 表示 model 一對一或多對一的關係

執行 belongs_to :user,會動態產生好幾個方法:user, user=

has_many

  • 是一個類別方法

  • 表示 model 一對多的關係

  • has_many 關聯會自動為關聯的模型生成複數形式的方法

      // app/models/comment.rb
    class Comment < ApplicationRecord
    belongs_to :post
    end

    @comments 是一個由 has_many 生成的集合,其中包含與該文章相關聯的所有評論

      // app/controllers/posts_controller.rb
    class PostsController < ApplicationController
    def show
    @comments = @post.comments //所以這裡才會是複數形式
    end
    end

    局部渲染:

    在 Posts 的 Show 視圖中渲染 Comment Partial :

    <!-- app/views/posts/show.html.erb -->

    <%= render partial: 'comment', collection: @comments %>

    Comment Partial(_comment.html.erb):

    <!-- app/views/comments/_comment.html.erb -->
    <div class="comment">
    <p><%= comment.body %></p>
    </div>
    note

    使用 render partial: 'comment', collection: @comments 時,Rails 會遍歷 @comments 中的每個評論對象,並將它們傳遞給 _comment.html.erb,以生成相應的 HTML 片段

has_one :through

  • 表示 model 建立間接一對一關係

在這個例子中,User 通過 Profile 這個中介 model,建立了和 Address 的一對一關係。這樣一來,你可以透過 user.address 直接訪問 Address model,而不需要直接在 User model 中定義 belongs_to 關係

# app/models/user.rb
class User < ApplicationRecord
has_one :profile
has_one :address, through: :profile
end

# app/models/profile.rb
class Profile < ApplicationRecord
belongs_to :user
has_one :address
end

# app/models/address.rb
class Address < ApplicationRecord
belongs_to :profile
end

has_many :through

  • 表示 model 多對多的關係
  • 需要第三個資料表來儲存兩邊資訊

假設有學生(Student)、課程(Course),以及一個中介模型 Enrollment 來建立學生和課程之間的多對多關係。可以這樣定義 model :

# app/models/student.rb
class Student < ApplicationRecord
has_many :enrollments
has_many :courses, through: :enrollments
end

# app/models/course.rb
class Course < ApplicationRecord
has_many :enrollments
has_many :students, through: :enrollments
end

# app/models/enrollment.rb //第三個資料表
class Enrollment < ApplicationRecord
belongs_to :student
belongs_to :course
end
  • has_and_belongs_to_many (HABTM)
    • 表示 model 多對多的關係

使用這種方式建立多對多關係時,必須建立一個中間表,以儲存兩者之間的關係。在這個例子中,中間表的名稱應該是 courses_students (migration),按照字母順序排列並使用複數形式。這個表會存儲 student_id 和 course_id 之間的對應關係

# app/models/student.rb
class Student < ApplicationRecord
has_and_belongs_to_many :courses
end

# app/models/course.rb
class Course < ApplicationRecord
has_and_belongs_to_many :students
end

Scope

  • 簡化使用時的邏輯

  • 簡化 Controller 的程式碼

  • 類別方法

    class Article < ApplicationRecord
    def self.published
    where(published: true)
    end
    end

在 model 定義了一個名為 published 的 Scope,使用了 where 方法,表示我們只想獲取 published 屬性為 true 的文章

class Article < ApplicationRecord
// 定義一個名為 published 的 Scope
scope :published, -> { where(published: true) }
end

在 Controller 中調用 Scope

class ArticlesController < ApplicationController
def index
# 使用 Scope 獲取所有已發布的文章
@published_articles = Article.published
end
end

Action Mailer

在 Rails 中,Mailer 是處理郵件發送的部分。可以使用 Mailer 來生成和發送電子郵件,那要怎麼查詢 rails 的內建功能呢? 在終端機輸入 rails g 這樣就可以知道 rails 內建有什麼功能囉,接著在終端機輸入 rails g mailer Vote 會新增兩個檔案:mailers/vote_mailer.rb & views/vote_mailer

mailers/vote_mailer.rb 的檔案裡,建立 vote_notify 的方法

class VoteMailer < ApplicationMailer
def vote_notify(email)
mail to: email, subject: 'test email'
end
end

當你想發送郵件時,可以在 controller 或其他地方呼叫這個 vote_notify 方法

class CandidatesController < ApplicationController
def vote
# send mail

VoteMailer.vote_notify('cocolulu2327@gmail.com').deliver

end
end

可以在 views/vote_mailer 目錄下創建與方法名對應的 view 文件

vote_notify.html.erb
<h1>test email</h1>

因為寄發信件需要配置 Action Mailer 的 delivery_method 和相應的 smtp_settings,以指定郵件的發送方式和 SMTP 伺服器的設置

在 rails guides 的 ActionMailer Basic 可以找到相關文件需要的資料,並且確保在 config/environments/development.rb 文件中配置郵件寄發服務:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
domain: 'example.com',
user_name: '<username>',
password: '<password>',
authentication: 'plain',
enable_starttls: true,
open_timeout: 5,
read_timeout: 5
}

delivery_method 設置為 :smtp,表示希望使用 SMTP 來發送郵件,這是因為 Action Mailer 可以使用不同的方式來發送郵件,例如 SMTP、Sendmail、Test(僅用於測試)等,而 smtp_settings 則包含了 SMTP 伺服器的詳細設置,包括伺服器地址、埠口、用戶名、密碼等

但由於安全性考慮,不可能將密碼放到程式碼中,所以需要寄發信件的套件,例如:mailgun,可以產生 username password port 及 hostname

ActiveJob

有些程式在執行的時間較長,等了一陣子後他才會運作到下一個工作,這樣不僅會影響到使用者體驗,更會在效能上出現很大的問題,所以像這種會影響使用者體驗的工作,會把要執行的工作先存起來,然後跑下一步,等到空檔或是指定時間再去執行存下來的工作,可以使用 rails 的內建功能 ActiveJob 來處理這個問題

根據以上寄發信件的例子來看:

執行 rails g job VoteMail 命令,生成一個新的 job

會在 app / jobs 目錄下創建一個新的文件 vote_mail_job.rb

class VoteMailJob < ApplicationJob
queue_as :default //表示工作著急程度:low_priority、default、urgent

def perform(*args)
VoteMailer.vote_notify('cocolulu2327@gmail.com').deliver
//把剛剛跑很慢的寄發信件貼過來
end
end

然後在 controller 的地方調度 Job

class CandidatesController < ApplicationController
def vote
# send mail

VoteMailJob.perform_later

end
end