# Ruby(09/14) 五倍紅寶石(第八屆共筆)(2021/8/16)
## Lighthouse 燈塔
可以從開發者工具看,分析評分
黃色以上才比較ok
## evernote 專案
### 讓刪除變假刪除
1. 新增 deleted_at 欄位
rails g migration deleted_at
再去db:migration檔案新增change方法跟增加欄位
最後db:migrate顯示
20210816115437_deleted_at.rb
```rb
class DeletedAt < ActiveRecord::Migration[6.1]
# 新增欄位: 資料表名稱,欄位名稱,型態
# 新增索引: 加快查詢,概念有點像寫書會加的附錄
def change
add_column :notes, :deleted_at, :datetime
add_index :notes, :deleted_at
end
end
```
2. 讓 index 直接在首頁顯示
routes.rb
```rb
# 讓 index 直接在首頁顯示
get "/", to: "notes#index"
resources :notes
```
3. 讓刪除變成假刪除
notes_controller.rb
```rb
第一種方式
def destroy
# @note = Note.find(params[:id])
# 控制能不能顯示,不要真的刪掉
# 讓他從原本的刪除變成更新資料 destroy 改 update
# 更新刪除的時間,預設都是 null 點了之後會變成 true
@note.update(deleted_at: Time.now)
redirect_to "/notes"
end
```
note.rb
```rb
第二種方式
# 定義類別方法
def self.available
where(deleted_at: nil)
end
```
```rb
第三種方式
scope :avaliable, -> { where(deleted_at: nil)}
# default_scope 所有的搜尋都會加上後面的條件
# 可以用 unscope 把他去掉
# 商業邏輯,以後如果要改需求改這邊就好
# 把刪除時間加進去讓他看起來是被刪掉的
# 用 where 過濾,沒點刪除的都是 nil
更好的寫法:
default_scope { where(deleted_at: nil) }
取消scope的寫法:
Note.unscope(:where)
#意思是我要取消scope裡面的where方法
# 一般的 scope 還是要在類別後面加這個方法名稱,default不用 => 不用寫 available
# scope :available
# https://github.com/rubysherpas/paranoia
# 直接用 acts_as_paranoid 取代 default_scope { where(deleted_at: nil) }
# 使用前先在 Gemfile 寫入 gem "paranoia", "~> 2.2"
# 接著輸入 bundle install,如果沒反應就重開 server
acts_as_paranoid
```
### 註冊系統
1. 新增路徑
routes.rb
```rb
# get "/users", to: "users#profile"
# 對於使用者只要開建立使用者的路徑就好
# 不需要使用者列表(後臺看到就好)
resources :users, only: [:create] do
# 路徑: GET /users/sign_up 註冊表單
# 路徑: GET /users/sign_in 登入表單
# collection 追加路徑,方法 :方法名稱
collection do
get :sign_up
get :sign_in
end
end
end
```
2. 定義方法
users_controller.rb
```rb
def sign_up
@user = User.new
end
def sign_in
@user = User.new
end
# sign_up 送出後會送到 create
def create
@user = User.new(user_params)
# 不要存明碼到資料庫
# CRUD callback 要知道
if @user.save
redirect_to "/"
else
render :sign_up
end
end
def profile
end
private
def user_params
params.require(:user).permit(:account, :password, :email)
end
```
3. view 顯示出來
sign_up.html.erb
```rb
<%# 直接跟他講路徑,不要讓他猜,可以用GET方法顯示使用者跟POST方法新增使用者 %>
<%# url: "/users" 可參考路徑對照表 %>
<%= form_for(@user, url: "/users") do |f| %>
<%# email 防呆 %>
<div class="field">
<%= f.label :email %>
<%= f.email_field :email %>
</div>
<%# password_field 讓他不顯示出來 %>
<div class="field">
<%= f.label :password %>
<%= f.password_field :password %>
</div>
<%# 密碼再確認 %>
<div class="field">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
</div>
<div class="field">
<%= f.label :account %>
<%= f.text_field :account %>
</div>
<%= f.submit %>
<% end %>
```
4. 驗證 + 加密密碼
user.rb
```rb
# 使用 Digest 方法要先 require 進來
require 'digest'
class User < ApplicationRecord
# 可以用 attr accessor 騙他,實際上沒有這個欄位(不建議)
# attr_accessor: :password_confirmation
# 不要在資料庫存明碼
# confirmation = 確認兩個欄位值是一樣的, uniqueness = 唯一值,
# 兩個都是驗證器
# https://guides.rubyonrails.org/active_record_validations.html
validates :email, presence: true, uniqueness: true
validates :password, presence: true, confirmation: true
validates :account, presence: true, uniqueness: true
# 各種 callback https://guides.rubyonrails.org/active_record_callbacks.html
# 放before_create 因爲怕會跟其他的 callback 重複,新增跟修改都會觸發
# before after 自己要看,知道他們在做啥
# 使用者被建立前先做加密
before_create :encrypt_password
# 手刻會員系統,面試可能會考
# 加密方法:
# MD5 已被破解
# SHA 比較安全
# 密碼只能被加密一次,兩次以上會看不懂
require "digest"
def encrypt_password
# Digest::MD5.hexdigest(pw)
# salting:在密碼裡面加東西,讓他比較複雜
salted_pw = "eiojre#{self.password}rewropwr"
self.password = Digest::SHA1.hexdigest(salted_pw)
end
end
```
### 登入系統
1. 新增路徑
routes.rb
```rb
# post :sign_in 拉出去做
# 登入跟註冊是兩件事情,註冊是一般的 CRUD,但登入會用到 sessions,所以把他獨立拉出來寫
# sessions 是某個 controller 的名字
# 寫入 sessions = 登入,用 as 換 path 名字,看起來比較乾淨
post "/users/sign_in", to: "sessions#create", as: "login"
# 刪除 sessions = 登出
delete "/users", to: "sessions#destroy", as: "logout"
```
2. 建 controller
rails g controller sessions
sessions_controller.rb
```rb
def create
# 先加密再驗證才會正確
# 發 session(cookie): 一種認證機制
# 拿到 cookie 不等於登入
# 登入 = 拿到 session 而且可以做裡面的事
# 登入成功會發令牌(cookie)給你,瀏覽器會留一個 session 做比對,看是否正確
# 會有時間性,如果過期要再重新登入
# 或是對方 server 重新啟動也要重新登入
# HTTP本身是沒有狀態的,在這邊做任何事情,接著跳到其他頁面再回來不會保留
# 透過 params 或是 cookie 知道前一頁的資料
# 可以把前一頁的資料存在 cookie 留到下一頁用
# localstorage = 大型 cookie
# 看你有 cookie 就讓你登入
# 沒有就不讓你登入
# 驗證 email & password
# 對照 user.rb 的
pw = user_params[:password]
salted_pw = "xyz#{pw}827128#{pw}82-12j23h"
hashed_password = Digest::SHA1.hexdigest(salted_pw)
user = User.find_by(email: user_params[:email],
password: hashed_password)
if user
# session 取名隨意
# 用 session 方法把 user.id 送進來 thankyou9527
session[:thankyou9527] = user.id
redirect_to "/"
else
redirect_to "/users/sign_in"
end
end
# 登出 = 把sessions刪掉
def destroy
session[:thankyou9527] = nil
redirect_to "/"
end
private
def user_params
params.require(:user).permit(:email, :password)
end
```
3. 同樣的放公版
views/layouts/
application.html.erb
```rb
<%# 如果要寫在別的地方要寫好路徑讓他找,建shared資料夾%>
<%#使用者登入跟沒登入會出現不同資訊%>
<%# 用render 去抓另外一個公版到這邊呈現,看起來比較不那麼亂%>
<%= render "shared/navbar" %>
<%= yield %>
```
_navbar.html.erb
```rb
<%# 共用的通常會創一個 shared 資料夾來放 %>
<nav>
<% if user_signed_in? %>
<%= link_to "登出", logout_path, method: "delete" %> |
<%= current_user.email %>
<% else %>
<%= link_to "登入", sign_in_users_path %> |
<%= link_to "註冊", sign_up_users_path %>
<% end %>
</nav>
```
4. 共用的 controller
application_controller.rb
```rb
# view helper 跟 controller 都會用到 helper_method 裡面的方法
# 用 helper_method 把 controller 功能匯出到 view helper,寫一次就好
# 或是寫在 view helper 再外掛模組進來也可以
helper_method :user_signed_in?, :current_user
private
# 有 session
def user_signed_in?
session[:thankyou9527] != nil
end
# 如果有用戶的 session 就可以登入
def current_user
if user_signed_in?
User.find(session[:thankyou9527])
else
nil
end
end
# 沒有 session 就要重登
def check_login!
if not user_signed_in?
redirect_to "/users/sign_in"
end
end
```
5. 要登入才能做新增修改刪除等動作
```rb
before_action :check_login!, except: [:index, :show]
```
## 套件
[devise套件](https://github.com/heartcombo/devise)
rails g devise MODEL
MODEL 要換字! 看你要取什麼名字
套件裡面的 Wiki 有各式套件,照抄就好,抄官網最好,別亂抄來路不明的文章
[五倍紅寶石 github 作業](https://github.com/5xTraining/TrainingPrograms/blob/master/T101.md)
步驟20登入登出功能 hen 重要
## 關聯性
- 1:1
- 1:N
- N:N
user has_many notes
notes belongs_to user
取名 User 變小寫 + id
user_id = FK
### 加 FK key
在終端機操作,產生 user_id
1. g migration name
- migration擋內新增欄位
- add_reference(:... ,:... ),產生 user_id以外還會加索引
20210816155453_add_user_to_note.rb
```rb
def change
# notes -> user_id
add_reference :notes, :user, foreign_key: true
end
```
2. db:migrate
### has_many
類別方法
不是建立關聯,建立關聯是 add_reference,has_many 會幫你建立 4 個方法!
不一定兩邊都要寫,看你有沒有需要用到那個方法
has_many :notes
.notes
抓到目標user
.notes=
belongs_to :user
---
###### tags: `Ruby` `Rails`