你好,我是富士大顆 Aiko
很久沒有 new 專案,面目可憎的 me
主要分幾個 part:
rails new Project's name
通常在 new 專案的時候,我們會把一些設定也補充在這個指令裡面,像是資料庫、使用的前端框架 e.g. bootstrap, tailwind
會寫成:
$rails new MyProjectName-d postgresql --css tailwind --webpack=stimulus
這段指令包含:-d postgresql
:指定 PostgreSQL 作為資料庫(沒特別寫,預設是 SQLite)--css tailwind
:使用 Tailwind CSS 框架--webpack=stimulus
:使用 Stimulus 作為 JavaScript 框架,並且設定 Webpack
啊我們目前就簡單做,在終端機輸入:$rails new cc_bookmarks
接著按下 enter 執行,Rails 就會長一長串檔案/資料夾,約莫 78 個,最貼心的是他也自動 git init
了
那預設最初你會在的位置是 main
接著我們到那個專案:$cd cc_bookmarks
用 VSCode 打開:$code.
或者有另外一個方法,當你 new 完專案之後就到你的資料夾,
拖曳這個資料夾到 VSCode 就好摟!
bundle install
安裝所有在 gemfile 裡的檔案
這些是目前檔案裡的 gem 列表:
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.2.2"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0.8"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
# Use sqlite3 as the database for Active Record
gem "sqlite3", "~> 1.4"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", "~> 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
# Use Redis adapter to run Action Cable in production
# gem "redis", "~> 4.0"
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false
# Use Sass to process CSS
# gem "sassc-rails"
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri mingw x64_mingw ]
end
group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"
# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
# gem "rack-mini-profiler"
# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
end
group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem "capybara"
gem "selenium-webdriver"
end
group
會分成 3 種環境:development
test
production
所以可以根據需求,把 gem 分門別類到指定的環境裡,例如有些 gem 並不需要在 production 環境中使用,就把gem 放在 dev 或 text group ;一旦切換環境,使用 $bundle install
就會按照需求安裝。
預設都會是 development 喔!
記得經常執行 bundle update 來確保所有的 gems 都是最新版本!
rails g model Model's name
g
是 generate
的縮寫,所以你喜歡長的就用 generate
吧
釐清一下需求,
這邊我們要的 model 基本就兩種:Book & Review
那我們就想一下兩個 model 的關聯性,正常情況下,Book 應該是 has_many :reviews
吧?
所以 Review belongs_to :books
那欄位名稱跟 data type 呢?
初步規劃,Model Book 的欄位會有:
轉換成 code 就會是:
t.string :title
t.string :author
t.string :description
t.string :publisher
t.integer :weeks_on_list
t.integer :rank_this_week
在終端機輸入:$rails g model Book
這邊有兩種新增欄位的方式:
$rails g model Book
$rails g model Book title:string author:string description:string publisher:string weeks_on_list:integer rank_this_week:integer
$rails g model Book title author description publisher weeks_on_list:integer rank_this_week:integer
$rails g model Book
,編輯那個 migration
檔t.string :title
t.string :author
t.string :description
t.string :publisher
t.integer :weeks_on_list
t.integer :rank_this_week
那 model Review 的欄位呢?
所以一樣輸入 $rails g model Review
看你想用哪種方式編輯欄位
最後我的 model Review 長這樣:
class CreateReviews < ActiveRecord::Migration[7.0]
def change
create_table :reviews do |t|
t.string :author
t.string :comment
t.references :book
t.timestamps
end
end
end
比較特別要提的是 t.references :book
這會為你的資料表新增一個名為 book_id 的欄位,以及一個外鍵(foreign key)約束,以連結到 books 表格的 id 欄位,來建立表格之間的「關聯」
約束力再強一點,可以補充成:t.references :book, null: false, foreign_key: true
所以我在 model 寫好了關聯,也還是要加這一行嗎?
對。這樣比較完整。chat GPT 說:
如果你只寫 t.references :book,則不會有這些額外的約束。這可能會使你的資料庫狀態更容易出錯,因為它允>許 NULL 值和任意 book_id。
簡單地說,模型中的關聯和遷移檔案中的 t.references 是兩個相互補充的部分,兩者都是需要的。模型關聯負責應用邏輯,而 t.references 負責資料庫結構和約束。
ok
那我們再來編輯這兩個 model 檔,以完成關聯性:
class Book < ApplicationRecord
has_many :reviews
end
class Review < ApplicationRecord
belongs_to :book
end
$rails db:migrate
這個是比較古老的寫法:$bundle exec rake db:migrate
如果有 seed 檔的話(seed 就是預先要填入的資料,在開發階段可能會是一些假資料,看呈現的狀況),指令會是:$bundle exec rake db:seed
同理可證 $rails db:migrate
也可行
執行完,就會有個 db/schema.rb 檔,記錄會自動更新所有 model 欄位
像這樣:
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_10_02_114904) do
create_table "books", force: :cascade do |t|
t.string "title"
t.string "author"
t.string "description"
t.string "publisher"
t.integer "weeks_on_list"
t.integer "rank_this_week"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "reviews", force: :cascade do |t|
t.string "author"
t.string "comment"
t.integer "book_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["book_id"], name: "index_reviews_on_book_id"
end
end
到了這個步驟,如果你輸入 $rails s
會是這個畫面:
然後,就沒有然後了
因為還沒有編輯 Controller & Route
rails g controller Controller's name(s)
注意這邊的 controller name 要是複數喔
一般來說 controller 名稱通常是 model 名稱的複數形式。
模型(model)是 Book,則對應的控制器(controller)一般會是 BooksController。
model 的要素是欄位
那 controller 呢?
當然就是 action 了
Rails 也有幾種不同的方式來新增 action
rails g controller Controller's name(s)
$rails g controller Books index show
get 'books/index'
get 'books/show'
以及會自動生成 index 跟 show 分別對應的 view 檔案
rails g controller Controller's name(s)
如果在生成 controller 時已經指定了 action(如 index
和 show
),則會在 config/routes.rb
檔案中自動生成對應的 route :
```ruby
get 'books/index'
get 'books/show'
```
如果之後手動在 controller 中添加了新的 action,則需要手動更新 config/routes.rb
檔案。
可以按照以下格式來新增:get '<url>', to: '<controller>#<action>'
```ruby
get 'books/new', to: 'books#new'
```
或者想要一次設定多個標準的 CRUD(建立、讀取、更新、刪除),可以使用 `resources` 方法:
```ruby
resources :books, only: [:index, :show, :new, :create]
```
這會是 index
、show
、new
和 create
這四個 action 相對應的 route。
訂製 Route:
如果有特殊的 route 需求,也可以訂製,例如:
get 'books/popular', to: 'books#popular'
這樣你要記得在 books controller 裡面新增 popular 的 action
也逐漸明白為什麼不選擇在指令執行一起新增 action
因為 route 還要改!
雖然 rails 幫你新增了:get 方法的 url
但我們這個專案要帶 id
所以會噴錯喔
使用指令$rails routes
可以查到所有的 routes
如果太多,真的很難看的話
可以用指令 $rails routes | grep book
來篩選有 book 的 route
action 新增之後, block 會是空的,
所以我們要把 block 寫出來!
目前這個專案比較單純,只有兩個 actions
其實 action 要編輯的主要目的是要讓 view 知道,rend 的是什麼東西。
像這邊這樣的專案,index 就是所有的書
所以會這樣寫:
def index
@books = Book.all
end
這邊的 code 表示,我們用 @books 裝了 Book model 裡的所有資料
所以對照的 app/views/books/index.html.erb
就可以用 @books 來 rend 資料
show 比較複雜一點,但先不要緊張:
def show
@book = Book.find(params[:id])
@reviews = @book.reviews
end
這邊預計是單本書的 show 但為了要找書,我們定位的是書的 id ,而這是唯一值所以不會搞錯書params
會幫我們抓取 url 裡的 id,啊因為 params 是 hash-like 的物件,要拿key id 的 value
就要用[:id]
然後會可以看到該書的所有 reviews
所以這邊使用了兩個實體變數,@book & @reviews
讓 app/views/books/index.html.erb
這邊可以 rend 資料
比較要注意的是,通常因為我們需要新增修改原始資料,所以要 params 資料,因此要在這個 controller 底下新增 private def
params:
private
def book_params
params.require(:book).permit(:title, :author, :description)
end
def set_book
@book = Book.find(params[:id])
end
params 本身在 Rails 裡是一個 hash-like object,用來儲存從客戶端來的參數。
其中 book_params 方法通常用於 create
和 update
action,來過濾客戶端傳來的參數(也就是需要拿到 POST, PUT & PATCH 操作前的資料),防止有心人士透過所謂的 Mass Assignment 攻擊,使資料庫裡寫入不應該被寫入的欄位;
而 set_book 常常用於需要找到特定書籍的 action,如 show
、edit
、update
和 destroy
。
但因為我們這個專案還很單純,所以可以先不寫這兩個 private def
所以如果不選擇在指令執行就一起新增 action
這邊就要記得手動新增 view 檔app/views/books
在 books 資料夾裡面新增有的 action.html.erb
日後你有新的專案,也就是把 books 換掉,因為 books 這邊是我的 controller 名稱~
rails s
= rails server
啟動專案看看吧 連上 http://127.0.0.1:3000/
取消的指令是 control+C
接著就會是無限的除錯輪迴,但是不要害怕,問題都是可以解決的
以上是今天 CodeCademy 的專案實作練習!
同步一起放在 gitHub