# 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`