Rails Web應用開發實戰-學生選課系統基礎版(二)
在上一篇教程中,我們在Cloud9中跑通了整個演示代碼,下面我們將從零建立一個新的Rails應用。
在我們寫代碼之前,我們先看看Rails框架的結構:
Rails框架最主要的特點是遵循MVC的設計模式(M:model,V:view,C:controller),也就是控制器-視圖-模型模式。Rails框架將代碼邏輯實現在控制器(controller)中,將與數據庫的操作實現在模型(model)中,將與用戶交互的網頁元素(HTML、JS、CSS)實現在視圖中。通過分離這三大部分的功能,Rails能很好的維持一個清晰的架構。
在上圖的第一步中還遺留了一個routing(路由)的過程。
我們利用一個完整的過程來闡述上述過程,假設用戶在瀏覽器中輸入這個網址並提交:
http://whatever.com/users/index
,當服務器(whatever.com)接收到這個請求,將會把請求交給控制器users_controller
中的方法index
處理,爲什麼服務器知道如何把這個請求交給這個控制器以及這個控制器下的index
方法呢?
是因爲Rails有個routes.rb
文件,裏面配置了請求的映射關係,因此Rails在每次處理用戶請求時,都會去這個文件裏查找映射關係,再根據映射的結果交給合適的控制器以及控制下的方法,在這裏users
叫做資源,index
叫做處理資源的方法。這個過程就叫做路由。Rails在表示資源的時候利用了RESTful的理念。
1.創建新的Rails應用
1.在C9登錄後的界面中點擊創建新的項目,如下:
以上的配置會默認創建新的Rails App
2.C9爲我們生成好了一個Rails框架的文件模板,首先我們看一下文件樹:
- controllers:存放控制文件的目錄
- models:存放模型文件的目錄
- views:存放視圖文件的目錄
- assets:存放前端文件,如CSS,JS等
- initializers:Rails框架在啓動前,會自動加載initializers中的文件,初始化一些外部庫的配置
- database.yml:描述了與數據庫連接的配置,如使用何種數據庫,數據庫名和密碼等等
- routes.rb:路由文件,描述瞭如何將用戶請求交給特定的控制器處理
- db:存放數據庫相關文件,包括數據遷移文件和種子文件
- gemfile:描述了所需要的外部庫,
bundle install
這個指令就是安裝所有在這個列表下的外部庫。
3.點擊Run Project,我們就可以運行這個新的Rails App
如果理解Rails框架有困難,請先看官方指南-簡易博客
2.創建模型(Model)
Rails開發流程是先創建模型,再創建相應控制器,配置相關路由,然後在控制器下創建相應方法,最後對應每個方法創建視圖。
模型是什麼,模型代表了一個底層數據表的類,封裝了操作這個數據表的各種方法。
1.由於是學生選課系統,我們肯定需要一個數據表來儲存用戶的信息,需要一張數據表來儲存課程的信息,那麼我們運行以下指令創建用戶模型和課程模型:
pengzhaoqing:~/workspace $ rails generate model course
Running via Spring preloader in process 3251
invoke active_record
create db/migrate/20160920163906_create_courses.rb
create app/models/course.rb
invoke test_unit
create test/models/course_test.rb
create test/fixtures/courses.yml
pengzhaoqing:~/workspace $ rails generate model user
Running via Spring preloader in process 3262
invoke active_record
create db/migrate/20160920163927_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
- 我們只通過
rails generate model course
指令,Rails就幫我們創建了
db/migrate/20160920163906_create_courses.rb
、app/models/course.rb
、等test文件,省去了開發人員自己創建文件的麻煩。
2.現在我們到打開db/migrate/20160920163906_create_courses.rb
文件,填入:
class CreateCourses < ActiveRecord::Migration
def change
create_table :courses do |t|
t.string :name
t.string :course_code
t.string :course_type
t.string :teaching_type
t.string :exam_type
t.string :credit
t.integer :limit_num
t.integer :student_num, default: 0
t.string :class_room
t.string :course_time
t.string :course_week
t.belongs_to :teacher
t.timestamps null: false
end
end
end
- 這個文件描述了
courses
這個表中的字段和字段類型,Rails將會按照這個文件中描述的進行建立數據表。這裏我們並沒有配置database.yml
文件,Rails默認的就是內置的Sqlite3數據庫,可以自己去看看那個文件裏面的adapter
是不是指定的sqlite3
3.相同地,我們打開db/migrate/20160920163927_create_users.rb
,填入:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.string :num
t.string :major
t.string :department
t.string :password_digest
t.string :remember_digest
t.boolean :admin, default: false
t.boolean :teacher,default: false
t.timestamps null: false
end
add_index :users, :email, unique: true
end
end
- 因爲用戶可以分爲學生,老師和管理員三個角色,因此在用戶的字段裏,我們用
admin
和teacher
布爾類型的字段來區分三種角色。 add_index :users, :email, unique: true
表示我們爲users
表的email
字段建立索引,爲以後能快速地根據用戶郵箱檢索到某個用戶。
4.在選課系統中,一個學生有多門課,一門課可以被多名學生選擇,因此學生和課程就是多對多的關係,而且,每個學生的每一門課都有一個成績。這裏涉及了三個數據實體,一個是學生,一個是課程,一個是成績。所以,我們還需要創建一個關於成績的數據表,裏面儲存着學生表的主鍵(user_id)和課程表的主鍵(course_id)以及對應的成績。
下面,我們運行rails generate model grade
建立成績模型:
pengzhaoqing:~/workspace $ rails generate model grade
Running via Spring preloader in process 1144
invoke active_record
create db/migrate/20160921051153_create_grades.rb
create app/models/grade.rb
invoke test_unit
create test/models/grade_test.rb
create test/fixtures/grades.yml
進入db/migrate/20160921051153_create_grades.rb
,建立相應的字段:
class CreateGrades < ActiveRecord::Migration
def change
create_table :grades do |t|
t.belongs_to :course, index: true
t.belongs_to :user, index: true
t.integer :grade
t.timestamps null: false
end
end
end
這裏的
t.belongs_to: :course
就等於t.integer :course_id
,記錄了課程表中課程的id。如此表示的意圖在於更加清晰地表達模型之間的從屬關係:一個課程擁有多個成績,一個成績只屬於一個課程;一個學生擁有多個成績,一個成績只屬於一個學生;學生和課程兩個屬性能唯一確定成績
5.分析到這裏,我們是不會遺忘了什麼,對,就是這個:一個老師上多門課,每一門屬於一個老師。這裏老師和課程是也是一對多的關係(爲了簡化,就不再設計爲多對多的關聯關係了),回顧我們在課程模型的數據遷移文件db/migrate/20160920163906_create_courses.rb
中寫的一句代碼t.belongs_to :teacher
,就已經將用戶(老師)的id作爲外鍵儲存在課程模型的數據表中,目的就是要實現用戶(老師)與課程模型的一對多關係,即在課程模型的數據表中查找某個用戶(老師)的id,就能將取出與這個id相關的所有課程的數據。
6.我們直接畫出ER圖來看各個模型之間的關係:
- 理解這個圖需要一定的數據庫知識,更加詳細的在 Rails Active 關聯。
需要注意的是,上圖中,用戶模型和課程模型通過成績模型關聯起來了(因爲可以根據user_id查詢所有相關的course_id,而且根據course_id也可以反過來查詢所有的user_id),也就實現了用戶模型和課程模型多對多的關係。
7.到這裏,我們已經完成了模型數據字段的建立過程,下面我們要把這些字段之間的關聯關係告訴Rails框架,然後Rails就能自動幫我們組織各模型之間的關聯關係。首先打開app/models/course.rb
,填入:
class Course < ActiveRecord::Base
has_many :grades
has_many :users, through: :grades
belongs_to :teacher, class_name: "User"
validates :name, :course_type, :course_time, :course_week,
:class_room, :credit, :teaching_type, :exam_type, presence: true, length: {maximum: 50}
end
- 這裏,課程模型通過
has_many
方法描述與其他模型的一對多的關聯關係。through
參數描述了:此一對多關係是通過成績模型才關聯到用戶模型的。 belongs_to
方法描述了與其他模型的多對一的關聯關係,class_name
指定了用戶模型的類名,這樣Rails才能正確找到關聯的用戶模型。validates
方法指定了對模型下的字段的驗證,這裏一共驗證了:name, :course_type, :course_time, :course_week, :class_room, :credit, :teaching_type, :exam_type
8個字段,驗證要求是presence: true, length: {maximum: 50}
,表示各個字段要存在(不爲空)而且最大長度不能超過50個字符。驗證會在每次保存課程數據的時候執行
8.下一步,打開app/models/grade.rb
,填入:
class Grade < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
- 指定了與課程模型和用戶模型一對多的關係
9.接着打開app/models/user.rb
,填入:
class User < ActiveRecord::Base
before_save :downcase_email
attr_accessor :remember_token
validates :name, presence: true, length: {maximum: 50}
validates :password, presence: true, length: {minimum: 6}, allow_nil: true
has_many :grades
has_many :courses, through: :grades
has_many :teaching_courses, class_name: "Course", foreign_key: :teacher_id
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: {maximum: 255},
format: {with: VALID_EMAIL_REGEX},
uniqueness: {case_sensitive: false}
#1. The ability to save a securely hashed password_digest attribute to the database
#2. A pair of virtual attributes (password and password_confirmation), including presence validations upon object creation and a validation requiring that they match
#3. An authenticate method that returns the user when the password is correct (and false otherwise)
has_secure_password
# has_secure_password automatically adds an authenticate method to the corresponding model objects.
# This method determines if a given password is valid for a particular user by computing its digest and comparing the result to password_digest in the database.
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def User.new_token
SecureRandom.urlsafe_base64
end
def user_remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
def user_forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def user_authenticated?(attribute, token)
digest = self.send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
private
def downcase_email
self.email = email.downcase
end
end
- 用戶模型裏,注意:
has_many :courses, through: :grades
has_many :teaching_courses, class_name: "Course", foreign_key: :teacher_id
–這裏,兩句代碼都指定了與課程模型的一對多關聯關係,然而第一句並沒有指定課程模型的名字和課程模型中的外鍵字段,但是Rails卻能工作正常。這體現了Rails一個特性:convention over configuration (約定大於配置),意思就是說只要開發人員按照一定的約定模式寫代碼,就可以省去很多編寫xml文件來指定文件路徑的麻煩。這裏的has_many :courses, through: :grades
,Rails在解釋代碼的時候就會默認去找有沒有叫course.rb(複數變單數)的文件,而且還會去這個course模型下找有沒有叫user_id的外鍵字段。但是,根據第二句代碼中的teaching_courses
,Rails無法正確找到course.rb
文件(這就不是僅僅是複數變單數那麼簡答了!),所以我們需要手動指定它去找一個叫做Course類的模型和關聯模型下的外鍵teacher_id
- 後面定義的方法,例如
digest,new_token,user_remember,user_forget, user_authenticated?, downcase_email
都是在後面用戶登錄功能需要用到的標準功能,詳細內容請進傳送門
9.所有的模型和模型的數據遷移文件我們都建立好了,下面運行數據遷移rake db:migrate
建立數據表,成功會看到以下信息:
pengzhaoqing:~/workspace $ rake db:migrate
== 20160920163906 CreateCourses: migrating ====================================
-- create_table(:courses)
-> 0.0079s
== 20160920163906 CreateCourses: migrated (0.0080s) ===========================
== 20160920163927 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0014s
-- add_index(:users, :email, {:unique=>true})
-> 0.0011s
== 20160920163927 CreateUsers: migrated (0.0028s) =============================
== 20160921051153 CreateGrades: migrating =====================================
-- create_table(:grades)
-> 0.0039s
== 20160921051153 CreateGrades: migrated (0.0042s) ============================
結束語
到此,所有關於模型(model)部分已經完結了,模型部分主要是對數據進行操作,包括數據表的建立,數據表之間的關聯關係。
有困難的同學請先看看
下一章將會講解控制器(controller)部分。