開啟終端機建立新rails專案 $rails new dcard
建立後$cd dcard
進入專案資料夾
建立model$rails g model Board title
$rails db:migrate
migrate完成pic
$rails s
啟動rails server連上http://localhost:3000 看到歡呼畫面
開啟vs code
連上http://localhost:3000/boards出現錯誤畫面pic缺少路徑
在vs code裡找到/DCARD/config/routes.rb
加上路徑 resources :boards
建立boards的七個路徑八個action
再回到http://localhost:3000/boards錯誤畫面換成缺少BoardsController pic
開啟新終端機$rails g controller Boards
pic
錯誤畫面變成缺少action index pic
在BoardsController建立index方法
錯誤畫面變成缺少view
在/views/boards建立index.html/erb
在index.html/erb
new_board_path為new的路徑(查表得知)
按下頁面的新增看板出現缺少new方法
在BoardsController建立new方法
在/views/boards建立new.html/erb
在new.html.erb做新增看板的表單
按下新增後出現缺少create
在BoardsController增加create方法
在這裡我們不需要create的view所以用redirect_to '/'
導回首頁
但是現在首頁是Rails的預設歡呼畫面
所以在routes.rb用root "boards#index"
把首頁導向我們要的頁面boards/index
(root是rails用來引導首頁)
接下來繼續完成create方法
(params[:board]==>{"title" => 'ccc'})
params['board'] = params.require(:board)
再次新增看板後出現
ActiveModel::ForbiddenAttributesError in BoardsController#create
這是因為觸發rails內的保護機制
因為我們一次性把全部資料做輸入,其中可能有帶有惡意的資料
所以在這裡我們要對資料做清洗的動作
clear_params = params.require(:board).permit(:title)
新設定一個變數clear_params指定為只接受board中的title
原來@board = Board.new(params[:board])
中的params[:board]也就要改為clear_params
經過清洗後就可避免觸發保護機制
再來我們要設定新增看板的驗證機制
在/model/board.rb建立看板名稱必填並最少4字的規則
然後在create增加提示
notice: "新增成功"
為快閃訊息的省略寫法
(原寫法為flash["notice"] = "新增成功"
)
但目前這樣還不會顯示出快閃訊息
還要在/views下建立/shared/_flash.html.erb加上
<p><%= flash["notice"] %></p>
或簡寫<p><%= notice %></p>
這種_開頭的檔案稱為局部宣染檔案
要在/views/layouts/application.html.erb的body加上
<%= render "shared/flash" if flash[:notice] %>
在有快閃訊息時載入_flash.html.erb讀取路徑不用加_與副檔名
接下來我們希望新增失敗時可以保留失敗時輸入的值
我們已在create中在失敗時導回new.html.erb
new.html.erb中的<%= form_for(Board.new) do |board|%>
改為<%= form_for(@board) do |board|%>
現在會出現找不到@board的錯誤
我們再回到boards_controller的new方法
把@board指定為原來的Board.new
這樣看板新增失敗時就會保留原來輸入的值
這是因為我們刻意營造的巧合
原本new.html.erb的@board是代入new方法的@board = Board.new
但是在新增失敗時的@board
已經是create的@board = Board.new(clear_params)
正好就是我們輸入的值
接下來在index.html.erb中顯示我們已經新增的看板列表
目前會找不到@board因為還沒定義
所以在boards_controller的index
用Board.all取出全部看板
回到index.html頁面就會看到全部新增的看板
再來為看板增加連結讓點取看板時能連到個看板的頁面
將<%= board.title %>
加上連結改寫為
<%= link_to board.title, board_path(board.id)%>
board_path可以從查表得知,board.id則是各看板的id
然後點下看板名稱就會再出錯,因為又缺少了action 'show'
再回到BoardsController增加show方法
在/views/boards建立show.html.erb寫入
讓每個看板的頁面可以顯示看板名稱
接下來是修改功能
回到index頁面
在看板名稱前一行寫<%= link_to '修改', edit_board_path(board.id)%>
路徑edit_board_path同樣查表得知
點擊修改再次出現缺少edit的錯誤
回到BoardsController建立edit方法
在/views/boards建立edit.html.erb寫入
其實跟new.html.erb幾乎一樣,只是把新增改成編輯
回到edit頁面按下編輯又出錯缺少action 'update'
回到BoardsController建立update方法
又跟create很像
再來是刪除功能
回到index頁面
在看板名稱前一行寫
<%= link_to "刪除",board_path(board.id) ,method: 'delete'%>
路徑board_path同樣查表得知
點擊修改再次出現缺少destroy的錯誤
回到BoardsController建立destroy方法
刪除功能相對單純許多只要用@board.destroy就刪除了
刪除後再導回首頁,root_path 是rails內建的首頁路徑
現在只要一按刪除就真的刪除了
為了避免誤觸再追加一個提示訊息data: {confirm: "是否刪除"}%>
加在index.html.erb的刪除連結後
<%= link_to "刪除",board_path(board.id) ,method: 'delete', data: {confirm: "是否刪除"}%>
這樣一來每次點擊刪除都會出現是否刪除的提示
=================================
Board.find(1)
只能接數字
Board.find_by(id: 1,email: 'aaa@aaa.aa')
find_by(搜尋的欄位: 欄位的值)
Board.find_by(id: 1)意思為從id欄位中找尋值為1的Board
(id: 1,email: 'aaa@aaa.aa')
是個hash
Board.where(id: 1,email: 'aaa@aaa.aa')
類似find_by
find,find_by只能找一筆資料,where可以找多筆資料回傳的資料是陣列
在查詢不到資料時find會出現錯誤,find_by會回傳nil不會報錯,find_by!才會報錯,而where會回傳一個空陣列也不會報錯
=================================
rescue_from ActiveRecord::RecordNotFound with: :record_not_found
原來寫法rescue_from(情況 {with: :方法名稱})
(情況 {with: :方法})
是參數
{with: :方法}
是個HASH
在這裡是指遇到ActiveRecord::RecordNotFound時用record_not_found方法進行處理
record_not_found內容為讀取404.html,取消公版的layout,回報網頁瀏覽懶器狀態為404
寫在ApplicationController裡可以讓所有Controller使用
=================================
可以寫在new和edit頁面,在新增或修改失敗時列出錯誤訊息
=================================
整理程式碼
可以發現show,edit,update,destroy都有
@board = Board.find(params[:id])
所以可以在private新增成一個方法
然後在最前面加上
before_action :find_board, only: [:show, :edit , :update , :destroy]
這樣在指定的action都會執行@board = Board.find(params[:id])
同樣clear_params = params.require(:board).permit(:title)有重覆使用也可以在private建立方法
這裡不用before_action是因為create和update裡的clear_params已經直接呼叫方法了
show,edit,update,destroy尤其是show,edit已經空空如也,不用before_action根本不知道要做什麼
再來new.html和edit.html也幾乎一樣,就在同資料夾的/boards建立_form.html.erb貼上
但我們把@board全部拿掉@改成board,因為局部宣染的檔案會使用多次,所以在呼叫使用時再塞入資料,才能正確呈現每個頁面,讓局部宣染的檔案自己抓資料可能抓不到該頁面要用的資料
在new.html和edit.html用render呼叫局部宣染_form.html.erb
<%= render 'form' %>
這時候會出錯,因為我們把@拿掉了,要把@board重新補回去
<%= render 'form' ,board: @board%>
在index,html中
<%= link_to board.title, board_path(board.id)%>
.id是可以省略的,就變成
<%= link_to board.title, board_path(board)<%= link_to board.title, board_path(board)
又因為是查詢同名的單筆資料board_path(board)
可以再省略成board
最後就變成<%= link_to board.title, board %>
同理
<%= link_to "刪除",board_path(board.id) ,method: 'delete', data: {confirm: "是否刪除"}%>
也可以省略成
<%= link_to "刪除", board ,method: 'delete', data: {confirm: "是否刪除"}%>
=======================================
註冊 登入 功能
先在routes.rb
建立註冊路徑get '/users/sign_up', to: 'registrations#new'
這裡生成的路徑為users_sign_up_path也可以做更改
get '/users/sign_up', to: 'registrations#new, as: 'registration'
這樣一來路徑就會變成registration_path
在nvabar就可以新增註冊的聯結
<li><%= link_to '註冊',registration_path %></li>
連線到http://localhost:3000/users/sign_up
出現uninitialized constant RegistrationsController
我們可以在vs code手動建立或用$rails g controller Registrations
建立RegistrationsController
接下來錯誤會變成缺少new action和new.html頁面
直接在registrations_Controller.rb
以及在views下新增資料夾registrations及new.html.erb
用$rails g model User email password nickname
建立使用者User的model裡面有email password nickname三個欄位
在new.html.erb裡
密碼使用password_field可以變成星號顯示
password_confirmation是驗證密碼的欄位
出現NoMethodError in Registrations#new
因為@user預設路徑users_path對應users#create,但從路徑對照表上可以發現並沒有這條路徑,我們是使用registrations_controller而不是user_controller
所以我們直接在routes.rb新增路徑給他post '/users', to: 'registrations#create'
再到路徑對照表就可以發現出現users_path對應registrations#create
輸入資料按下註冊後出現錯誤缺少create action
再回到registrations_controller加上create
類似前面board的create,要加入:password_confirmation,否則不會驗證,這裡的new會找同名controller資料夾的new
接下來我們用$rails c
進入rails console
用$User.first
來看第一個使用者的資料
可以看到password: [FILTERED],FILTERED只是rails隱藏起來的表示方法,實際上並沒有加密密碼,用SQLite打開資料庫就可以一覽無遺
在user model加上驗證規則,email password nickname都為必填,另外email必須是唯一
format是rails中的一種驗證器,後面接的一長串稱為常規表示法
在password加上confirmation: true就會驗證password與password_confirmation是否相同(慣例)
再來做密碼加密
在routes.rb
之所以用before_create是因為密碼只有在註冊時會加密一次,如果用before_save會變成每次有資料存入都會加密一次
===========================
登入
先在user.rb建立路徑
然後再路徑對照表中找到session_path對應sessions#new
所以就可以將<li><%= link_to '登入', session_path %></li>
加入_navbar.html.erb,點擊登入後會出現錯誤uninitialized constant SessionsController,所以我們就要建立SessionsController,然後又是前面做過的建立new action與new.html.erb
在new.html.erb中
然後又出錯ArgumentError in Sessions#new
所以在SessionsController中
但是我們在這裡並沒有要真的新增使用者,只是為了消除ArgumentError in Sessions#new而已
在<%= form_for(@user) do |form| %>
中會照CRUD方式走向users_path,但實際上我們要走的從路徑對照表中得知是login_path對應sessions#create,所以
<%= form_for(@user) do |form| %>
要加上url變成
<%= form_for(@user, url: 'login_path', method: 'post') do |form| %>
==============
form_for(MODEL)
form_tag(url)
上面兩個都會做出表單,form_for一定要接model,form_tag就處理與model無關的
在rails 5之後出現form_with來取代,以AJAX來傳遞資料(js會介紹)
form_with(model: @user)
form_with(url: '/')
==================
又可以改成<%= form_with(model: @user, url: 'login_path', method: 'post') do |form| %>
(不過在這裡其實只是要比對資料而沒有要真的新增user)
按下登入後畫面靜止不動(其實是有動從終端機可看到錯誤ActionNotFound
在
(The action 'create' could not be found for SessionsController),這是因為form_with的AJAX,可以再加上local: true
來使用post方式傳遞資料,所以寫成
<%= form_with(model: @user, url: login_path, method: 'post', local: true) do |form| %>
就會出現習慣的紅色錯誤畫面ActionNotFound
在SessionsController建立
因為find只能接id,所以我們在這用find_by,也不用find_by!因為找不到會出現404,但只需要導回登入頁重填即可
session是一個特別的東西,他會在伺服器建一個session同時發一個cookie給使用者,在這裡我們意思是發一張名為user9527的號碼牌,而號碼牌的內容為使用者user的email(但有session不代表有登入,登入是一個有點複雜的狀態,html不懂登入是什麼)
我們又會發現到即使輸入剛剛註冊的帳號也仍然會登入失敗,這是因為資料庫裡儲存的事已經加密的密碼,而我們在create中找的則是直接輸入為加密的密碼,所以要加上
pw = Digest::SHA1.hexdigest("a#{params[:user][:password]}z")
password: params[:user]
也要改成password: pw
所以整個create就變成
就可以正常登入了
再來我們來整理程式碼,因為在controller應該要只負責取東西,所以我們把商業邏輯有關的放model也可以方便重覆運用,所以把
放在user.rb定義成login方法
然後create就可以改成
在執行create時會代入params[:user]到user model裡的類別方法,用u來承接create的params[:user]
邏輯不要放在controller裡,controller只用來取東西,越乾淨越好
========================
登出
在routes.rb新增路徑
delete '/users/sign_out', to: 'sessions#destroy', as: 'logout'
修改_navbar.html.erb
判斷session[:user9527]存在時顯示登出否則顯示登入與註冊,另外登出要加上method: 'delete'則預設會使用get
現在我們就完成註冊登入登出囉
====================
我們還可以再整理程式碼
在SessionsHelper.rb
建立一個current_user方法,在session[:user9527]存在時,撈出email與session[:user9527]內email相同的使用者,否則為nil
所以_navbar.html.erb可以將session[:user9527].present?
改成current_user
就變成
current_user.nickname則可以印出使用者的nickname
但在這情況下每次頁面都會重新執行User.find_by(email: session[:user9527])
再撈一次資料(n+1問題),所以我們可以用||=改寫current_user方法
只有第一次執行時會進行撈資料的動作,同時將其值指定給@user,之後執行current_user就會直接回傳@user而不會再撈資料
a = b => a = a b,a沒有被初始化,或為nil或false時將b值指定給a,其他情況維持a的原值
1120 0517 devise
============
會員資料編輯
在routes.rb建立新的路徑
又發現可以用resource :user, controller: 'registrations'
也可以做出以上兩個路徑,再仔細看路徑對照表,可以看到也做出與post '/users', to: 'registrations#create'同樣的路徑,最後可以整理寫成
resource :user, controller: 'registrations',only: [:create, :edit, :update]
再來get '/users/sign_up', to: 'registrations#new', as: 'registration'
也同樣使用registrations_controller也可以一並整理進來變成
這樣修改後經由路徑對照表發現註冊的路徑也必需改成
<li><%= link_to '註冊',sign_up_user_path %></li>
再整理以下三行
但是這三行/users/sign_in和/login與/users/sign_out都不是經典的action,所以only放空陣列,將這三行全部改寫放進Block,其中login為了統一性在特別改寫成sign_in,
最後如下
但是這樣一來,登入與登出的路徑也要修改如下
在/sessions下的會員登入new.html.erb的也要改url
<%= form_with(model: @user, url: sign_in_users_path, method: 'post', local: true) do |form| %>
開啟rails s,連至http://localhost:3000/users/edit
發生錯誤The action 'edit' could not be found for RegistrationsController
在RegistrationsController建立edit action
在views/registrations建立edit.html.erb
一般使用者不需要知道id所以一開始是用resource不帶s來新增路徑,刀錢使用者則使用helper裡的current_user,同樣他不是經典的action,要從路徑對照表找出對應的路徑,添加url和method
按下更新後出現錯誤The action 'update' could not be found for RegistrationsController
建立update action
按下更新後出現錯誤undefined local variable or method current_user
這是因為current_user 是寫在SessionsHelper裡,而預設是view專屬的,在model和controller中都取不到
到sessions_helper.rb裡
可以發現SessionsHelper是一個模組(module),所以可以在RegistrationsController中用include SessionsHelper
,如果發現current_user很常被使用可以將include SessionsHelper寫在上一層的ApplicationController,這樣其下的controller都可以使用到current_user
修改email後按下更新出現錯誤ArgumentError in Registrations,因為我們的session當初是帶入email的,更新email之後就找不到了,用更新的email可重新登入
所以我們應該把session的內容改成使用者id,修改create
SessionsHelper也是放入email所以也要改成id
@_user9487 ||= User.find_by(id: session[:user9527])
這樣一來修改email就不會出錯了
接下來考慮到未登入時不該能使用編輯會員資料功能,所以在RegistrationsController裡加上 before_action :session_required, only: [:edit, :update]
session_required是自定義的判斷是否登入方法,在這裡只要求edit和update前要使用,至於session_required可以先寫在ApplicationController的private裡(要寫在哪憑經驗判斷,要讓每個需要的地方都能取用)
如果current_user不存在,則回到登入頁面並提示請先登入(if not可以用unless)
============================
在看板新增文章
先在routes.rb新增路徑,因為是在看板下寫文章,路徑也要在看板下才合理,所以在原來的resources :boards下擴充posts路徑
從路徑對照表對應posts#new的路徑為new_board_post_path,如果只小這樣還是會出錯required keys: [:board_id],因為不知道文章要加在哪個board上,所以要再加上@board
在boards/show.html.erb
接下來按下新增文章就會出現uninitialized constant PostsController
要新增PostsController與new action同時在views下建立new.html.erb
接下來要建立post的model欄位有title、content、borad_id、user_id,可以用belongs_to來建立borad_id、user_id,如果打成board_id:belongs_to會建立board_id_id的欄位
$rails g model Post title content:text board:belongs_to user:belongs_to
在migration可以看到belongs_to的效果
在post.rb
增加文章驗證,要有標題title與內文content
執行$rails db:migrate
在posts的new.html.erb增加表單
也要建立new action
再來還會報錯NoMethodError in Posts#new,因為post是建立在board下,所以以原來的CRUD規則會找不到路徑,要從路徑對照表找對應posts#create的是board_posts_path,同時還必須代入board的id,所以new.html.erb的@post改寫為
還要在PostsController建立@board否則會找不到
按下新增後出現The action 'create' could not be found for PostsController
建立create action
從log可以找到Parameters: {"authenticity_token"=>"oT3XwEIhL9S997ntl4LcC8+XRHw9cvWySCmdwBJ3SxWRoaxdIdDsa0o/fw7um0bNF4FP8vQUV+cvKz5cAwwKpA==", "post"=>{"title"=>"erth", "content"=>"argh"}, "commit"=>"新增文章", "board_id"=>"42"}
而我們只需要{"title"=>"erth", "content"=>"argh"}所以要清洗
create可寫成
但是仍然會失敗,用$rails c-- sanbox
模擬
$p1 = Post.new(title: 'aaa', content: 'ccc')
$p1.save
==>false
得到false
用$p1.errors.full_messages
印出錯誤訊息
得到["Board must exist","User must exist"]
所以我們必須提供board_is和user_id
有current_user就表示要驗證使用者是否登入
before_action :session_required, only: [:create]
換個角度
在user.rb
has_many :posts
每個使用者可以有很多文章,同理看板也是可以有很多文章
在board.rb
has_many :posts
has_many可以讓uesr跟board增加.posts功能
在rails c –sandbox中測試
可以直接用user(或board)的id在post中找資料
所以posts_controller中create原來的(普通寫法)
可以改成以board角度的
或是以user角度的
這兩種才是熟練關聯性的寫法
接下來就可以新增文章了~~
另外一開始在posts_controller中新增後回到首頁
我們可以改成回到文章看板
又因為board_path(@board)名稱相同可以用@board表示
接下來在看板顯示文章列表
在boards_comtroller的show中
以上兩種寫法較差
@posts = @board.posts
熟練的寫法(@board是之前before action已經建立)
在boards的show.html.erb
但是目前顯示的文章會是最新的在最下方
所以要在show action用order增加逆向排序
再來我們希望點擊文章標題可以連到文章內容,所以要給文章標題增加連結
<li> <%= link_to post.title,post_path(post) %> </li>
post_path(post)可在簡寫成post(同名),最後寫成
<li> <%= link_to post.title,post %> </li>
點擊文章標題後出現The action 'show' could not be found for PostsController
建立show action與show.html.erb
在show.html.erb
@post.user.nickname顯示文章作者暱稱
simple_format()一個helper讓文章換行,因為html只負責輸出畫面,對於空白或括弧沒有處理語法
:back是回上一頁的特殊寫法
讓文章作者才可看到編輯選項
盡可能不要再view有出現判斷式,最好用方法包起來
所以改成
<%= if current_user.own?(@post) %>
或
又單行時if可以寫到後方
<%= link_to '編輯', edit_post_path(@post) if @post.owned_by?(current_user) %>
方法建立在post.rb
self.user中的.user為belongs_to :user產生的方法
再來出現CRUD的缺少edit action與頁面
在posts_controller
發現同show方法
所以可以在privat建立
講show跟edit用before_action整理(update預期會用上)
before_action :set_post, only: [:show, :edit, :update]
同時在before_action :session_required
也要加上edit與update,未登入不可編輯和更新
在edit.html.erb借用新增文章new的form稍作修改
不用寫url因為觀察網址剛好符合CRUD,所以符合預設路徑
但是到目前為止有漏洞,只有檢查是否登入沒有檢查是否為作者,只要被使用者猜到網址就可以讓非文章作者進入編輯文章頁面,所以
before_action :set_post, only: [:show]
要拔掉edit與update
重新寫edit action
但這樣只能找到要編輯的文章,還沒確認使用者,所以
加上驚嘆號,讓找不到時讓上層controller捕捉噴錯404.html
更好的寫法以使用者角度
@post = current_user.posts.find(params[:id])
點擊更新文章噴錯The action 'update' could not be found for PostsController
在PostsController裡
============================
慣例
以上profiles的action分類很常見所以有慣例寫法
另外擴充路徑還有member與collection兩種
但是用resource不加s時,使用member一樣不會有id
namespace用法
============================
後續增加欄位
$rails g migration add_email_to_users
add_email_to_users不是固定寫法,只是一個檔名方便辨識加了什麼
執行後會在migrate資料夾做出一個空的檔案xxxxxxxxx_add_email_to_users.rb
加上想增加的項目add_column(model名稱,新增欄位,型態)
記得存檔後再執行$rails db:migrate
在schema.rb就可以看到新的email欄位建立好
=============================
遇到專案沒有migration時可以用$rails db:schems:load