你好,我是富士大顆 Aiko
今天來談面試題大魔王 MVC
Ruby on Rails 是一個高度生產力的 Web 框架,受到全端和後端開發人員的廣泛喜愛。Rails 專案是採用 Model、View、Controller(簡稱 MVC)的方式設計的,MVC 即模型(Model)、視圖(View)、控制器(Controller)。
MVC 模式是由 Trygve Reenskaug 在 1978 年所提出,是為程式語言 Smalltalk (聽說 Ruby 有借鏡其中的 message 特性)發明的一種軟體架構。
MVC 的特性讓專案在開發的過程中,各個模組權責分離,也較好維護及調整:
龍哥:
另一個好處,就是因為整個 Rails 專案都是遵循 MVC 的慣例結構,所以即使是不同程度的開發者寫出來的 Rails >專案,Controller 通常會放在 app/controllers 目錄裡,Model 應該也會放在 app/models 裡,不會有太大的差別。
Model 掌管資料,使用 ORM (Object Relationship Mapping)轉譯 SQL 跟資料庫溝通拿資料。 Controller 就是好多個樓層的好多櫃檯,來處理接收到的訊息往下去請 View 或是 Model 工作,算是滿重要的中控流程中心。View 則負責資料顯示,往往是 user 看到的畫面,可以是網頁、手機應用程式的介面,或者其他 user 互動的形式。View 不包含任何業務邏輯,它只是將 Model 中的資料呈現給 user,並接收來自 user 的輸入。
validates :name, presence: true
#存進去前,name 欄不能是空的
-- 庫存管理(Inventory Management): 確保購買的商品數量不超過庫存數量。
def purchase_item
if self.stock >= 1
self.stock -= 1
save
else
errors.add(:stock, "Out of stock")
end
end
-- 價格計算(Price Calculation): 計算商品的最終價格,考慮促銷折扣、稅等。
def final_price
self.base_price - self.discount + self.tax
end
-- 會員資格(Membership Eligibility): 根據 user 購買歷史或積分,決定他們是否有資格升級為 VIP 會員,等於 user 的某個欄位,例如 level 應該要被更新。 user.level # vip
def upgrade_to_vip
if self.total_purchase >= 1000
self.vip = true
save
end
end
-- 訂單處理(Order Processing): 在 user 下訂單後,進行一系列的檢查和操作,例如減少庫存、發送確認郵件等。
def process_order
purchase_item
send_confirmation_email if self.stock >= 1
end
以上都是業務邏輯的例子,它們通常會放在相對應的 model 中,以保持 controller 和 view 的簡單和清晰。這樣做還有助於改動和維護。可以輕易地在不同的 controller 或甚至不同的應用程式中重用清晰定義業務邏輯的 model。
在 Ruby on Rails 中,業務邏輯通常會以實例方法(instance methods)或是類方法(class methods)的形式呈現在模型(Model)中。這樣做可以保持業務邏輯和數據模型的緊密結合,並利於單元測試(unit testing)。
<!-- orders.rb -->
belongs_to :user
def total_price
order_items.sum(&:price)
end
#&: 是一種簡潔的語法糖衣,用於調用一個對象(obj)上的方法。它實際上是將一個 Symbol 對象轉換為一個 Proc 對象。這通常用於像 map、select、sum 等 Enumerable 方法中,當你想對每個元素調用同一個方法時。
#不使用會長這樣 order_items.sum { |item| item.price }
after_create :send_welcome_email
private
def send_welcome_email
UserMailer.welcome_email(self).deliver_now
end
scope :active, -> { where(active: true) }
然後你就可以像這樣使用這個範圍:
User.active
# 使用 ActiveRecord 查找 User
user = User.find(1)
# 使用 ActiveRecord 更新 User
user.update(username: "new_username")
# 父類
class Animal < ApplicationRecord; end
# 子類
class Dog < Animal; end
class Cat < Animal; end
class Article < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
end
# Article model 裡面有個 status 欄位
Rails 會自動為你生成一系列的 methods,方便操作這個 Article model
article = Article.new
# 查詢
article.draft? # => true 或 false
article.published? # => true 或 false
# 設定
article.published! # 將狀態設定為 "published"
# 作為查詢條件
Article.published # 回傳所有狀態為 "published" 的文章
ERB (Embedded Ruby): Rails Template Engine。
使用範例 (Usage Example):
<!-- 在 view 展示 username -->
<h1><%= @user.username %></h1>
例如,假設你有一個網站,其多個頁面上都有相同的 Navbar。不使用部件的情況下,可能需要在每一個相關的視圖文件中重複這個Navbar的 HTML 程式碼。這樣做會導致以下問題:
-- 程式碼冗餘(Code Redundancy): 要在多個地方維護相同的代碼。
-- 維護困難(Difficulty in Maintenance): 如果 Navbar 需要修改,你必須找到並更新所有重複的部分。
使用部件(Partials)能解決這些問題。你可以將 Navbar 的 HTML 代碼放到到一個名為_navbar.html.erb
的部件檔案中,然後在需要的視圖中引用它。
<!-- _navbar.html.erb -->
<nav>
<%= link_to 'Home', root_path %>
<%= link_to 'About', about_path %>
<!-- More links here -->
</nav>
引用方式:
<%= render 'navbar' %>
你也可以傳遞變數或參數到部件中,進一步提高它的重複使用性和抽象程度。
<!-- _product.html.erb -->
<div>
<h2><%= product.name %></h2>
<p><%= product.description %></p>
</div>
在要使用的 product 的 view 檔案裡
<%= render partial: 'product', locals: { product: @product } %>
//'product'會找到 _product.html.erb;需要的 product 會是 @product 而這個會寫在 controller 裡
(locals 在 Ruby on Rails 的視圍(View)和部件(Partials)中用來傳遞變數或資料。這個選項允許你將控制器(Controller)中的資料或主視圖(Main View)中的變數傳遞到部件(Partial)裡面去。)
# helper method
def formatted_date(date)
date.strftime("%Y-%m-%d")
end
其他的 view 就可以:
<%= formatted_date(@user.created_at) %>
使用 YAML 或其他格式的檔案,可以定義各種語言下相同詞語或句子的翻譯,然後在 View 或其他地方使用 t
或 translate
方法來顯示對應語言的文字。
# config/locales/en.yml
en:
hello: "Hello"
# config/locales/zh.yml
zh:
hello: "你好"
<%= t('hello') %>
在 View 中:
<%= t('hello') %>
根據用戶設定的語言,將會顯示 "Hello" 或 "你好"。
--
def show
@user = User.find(params[:id])
end
def index
@users = User.all
end
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new'
end
end
def show
render :show
end
CRUD (Create, Read, Update, Delete): 標準 RESTful 控制器動作。
首先,CRUD 是 "Create, Read, Update, Delete" 的縮寫,代表了數據持久層最基本的四種操作。在 Rails 的模型(Model)中,你會看到對應這四種操作的方法,例如 create, find, update, destroy 等。
RESTful 則是一種軟體架構風格,其核心思想是將所有事物抽象為資源,並通過 HTTP 方法(GET、POST、PUT、DELETE 等)對這些資源進行操作。
Rails 將 CRUD 操作映射到 RESTful 架構中,也就是說,你可以用 HTTP 的方法來執行 CRUD 操作:
Create 對應 POST
Read 對應 GET
Update 對應 PUT 或 PATCH
Delete 對應 DELETE
在 Rails 的路由(Routing)設定中,使用 resources 方法可以自動生成一套 RESTful 風格的路由,這些路由會對應到控制器(Controller)中相應的 CRUD 操作方法。
GET /articles(對應 index 方法,展示所有文章,對應 CRUD 的 Read)
GET /articles/:id(對應 show 方法,展示單一文章,對應 CRUD 的 Read)
POST /articles(對應 create 方法,新增文章,對應 CRUD 的 Create)
PUT/PATCH /articles/:id(對應 update 方法,更新文章,對應 CRUD 的 Update)
DELETE /articles/:id(對應 destroy 方法,刪除文章,對應 CRUD 的 Delete)
通常,在 Rails 的控制器中,你會看到名為 index, show, new, create, edit, update, destroy 等的方法,這些就是對應到 CRUD 操作。
因為 Rails 採用了 RESTful 架構,開發者可以更容易地預測和理解各種不同資源(模型)的 CRUD 操作方式,這大大提高了開發效率和維護性。總結來說,在 Rails 中,CRUD 和 RESTful 是相互緊密關聯的。Rails 採用 RESTful 架構,使得 CRUD 操作更加標準化和直觀。
過濾器 (Filters): 在執行動作之前或之後運行的方法(例如:before_action)。
比較常用的,像是驗證使用者是否登入:
before_action :authenticate_user!, only: [:edit, :update]
class UsersController < ApplicationController
before_action :find_user, only: [:show, :edit, :update, :destroy]
def show; end
private
def find_user
@user = User.find(params[:id])
end
end
def user_params
params.require(:user).permit(:username, :email, :password)
end
def show
@user = User.find_by(id: params[:id])
render '404' unless @user
end
def create
@user = User.new(user_params)
if @user.save
flash[:notice] = 'User created successfully.'
redirect_to @user
else
flash.now[:alert] = 'User creation failed.'
render 'new'
end
end
def index
@users = User.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @users }
end
end
# config/routes.rb
resources :authors do
resources :books
end
這樣,在 BooksController 裡,就需要找到對應的 Author:
def index
@author = Author.find(params[:author_id])
@books = author.books
end
如果後面有想到什麼會再更新,謝謝收看
我們明天見!
(本來以為明天再講 CRUD 結果一寫完全不能自己)(????)