ASTRO Camp 7th
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 0412 假刪除/ 使用者註冊/ 使用者登入 ###### tags: `Ruby / Rails` `bug待處理` ## 軟刪除/假刪除資料 - Soft Delete - 讓前端使用者看起來像資料已刪除,但是其實資料還存在資料庫中 - 新增欄位註記,在 View 顯示時用條件篩選掉資料 - ==不==使用 SQL 語法中的 DELETE 刪除資料庫中的資料。 - [Migration 修改筆記](https://hackmd.io/19RGr-SwTOaMUmlsWYNx0g#Rails%E8%A3%A1%E4%BF%AE%E6%94%B9Migration) ### 建立新的欄位 ```shell= $ rails g migration add_deleted_at (建立叫做 'add_deleted_at' 的空檔案) ``` * 用終端機建立 Migration 的慣例:[官方文件說明](https://guides.rubyonrails.org/active_record_migrations.html#creating-a-migration) ### 編輯 Migration 建立欄位 ```ruby= deleted: boolean, default: false # 同 add_column :restautants, :deleted, :boolean, default: false # 同 add_column :restautants, :delete_flag, :boolean, default: false # boolean 值標記 add_column :restaurants, :deleted_at, :datetime, default: nil # 時間戳記標記欄位 # 在 controller 撈資料的時候使用下面語法,這樣就可以篩掉已刪除的東西 Restaurant.where(deleted: true) # 同 Restaurant.where.not(deleted: false) add_index :restaurants, :deleted_at # 建立索引 # 降低資料增加後查詢速度的成長曲線,讀取速度比較快 # 缺點:效能變慢,寫入速度變慢,多佔記憶體空間 ``` - 上方第六行用的 `:datetime`:[API - datetime](https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html) - Migration 使用的方法:[官方文件說明](https://guides.rubyonrails.org/active_record_migrations.html#using-the-change-method) - 加入索引列的好處:[文章說明](https://dev.to/khalyomede/reduce-time-complexity-by-indexing-your-arrays-3ci6) - 索引頁 - 文件中如果有特定的內容或名詞是具有參考價值的,那麼通常會在文件的最後建立索引頁,以方便讀者查詢。 ### 編輯 View 讓清單只顯示未刪除部分 ```ruby= def index @restaurant = Restaurant.where(deleted_at: nil) end ``` ### 修改 Model 讓 controller 更乾淨 到 Model 裡直接修改 destroy 方法 ```ruby= class Restaurant < ApplicationRecord def destroy update(deleted_at: Time.now) end end ``` ### scope * 可視做簡化版類別方法 * 把一群條件整理成一個 scope ,賦予他意義,並簡化邏輯 * 減少在 Controller 裡寫一堆 Where 組合 * 預設回傳最後的結果,類別方法則可能回傳其他結果 ( 根據最後一行 ) * Scope 可連發 * 將商業邏輯包到 Model 裡面,簡化 Controller 要做的事情 * Scope 可以跟自己再組合出新的 Scope 方法 * Scope 會產生 self.方法名稱 ```ruby= scope :available, -> { where(deleted_at: nil, and permission: true) } scope :cheaper_than, -> (x) { where("price < #{x}")} scope :cheap, -> { available.cheaper_than(500) } ``` - 因為 block 本身不是物件,所以用 lambda, proc 將 block 物件化 - ==箭頭「 -> 」和逗號不能省略!!!== ### default_scope - 做任何查詢都會插入這個 scope(只要能夠有where語法的修改) - [官方文件說明](https://api.rubyonrails.org/v6.1.3/classes/ActiveRecord/Scoping/Default/ClassMethods.html#method-i-default_scope) - 只會有一個,但是在設定時裡面可以放多個指令 ```ruby= default_scope {where(deleted_at: nil)} # 可單獨存在 default_scope {where(deleted_at: nil).order(id: :desc)} # 多個指令 # ------ scope :available, -> { where(deleted_at: nil) } default_scope { available } # 或適當的做分類 ``` ### unscope - 不想要預設值就使用 unscope - 在指令後面加 `unscope(指令)` 就可以排除特定的預設指令 - 例: `Restaurant.all.unscope(:order)` 或 `Restaurant.all.upscope(:where)` - [官方文件說明](https://guides.rubyonrails.org/active_record_querying.html#unscope) - [unscope 手冊說明](https://apidock.com/rails/ActiveRecord/QueryMethods/unscope) ### 建立假刪除後又要真刪除 - 因為 destroy 已經被前面的 update 蓋掉了,所以要呼叫最近一層的同名方法,而最近一層的地方在於繼承ActiveRecord::Base ```ruby= def destroy super end ``` super本身是nil,沒有加括號的時候,將所有收到的沒有加括號的時候,將所有收到的參數往上丟,並呼叫上一層的方法,加括號的話可以控制給的參數數量 - 使用 Ruby 的 super 指令呼叫上==一層==的指令 - [super 手冊說明](https://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-super) >[假刪除paranoia 套件](https://github.com/rubysherpas/paranoia) ## 使用者註冊介面 ### 欄位設定 欄位|資料型態|說明 ---|---|--- email|string|unique password|string| role| string|[user/admin/staff] * uniq = unique 唯一值,比對索引值才會快,所以才會自動生成一個index,索引值是在硬體層直接對資料表做索引 ### 建立 新的model - User ```shell= $ rails g model User email:string:uniq password role ## 會建立一個叫 User 的 model,還有一個 migration ## email後面的string不能省略!!! ``` #### 產生的 migration 檔 ```ruby= class CreateUsers < ActiveRecord::Migration[6.1] def change create_table :users do |t| t.string :email # 也可直接在這裡接 ",unique: true" t.string :password t.string :role t.timestamps end add_index :users, :email, unique: true # 因為指令有下 uniq,框架會自動產出 end end # 總共會有 6 個欄位[users.email.password.role.timestamps*2(create_at.update_at)] ``` ### 建立 route 用 member 有 id 用 collection 沒有 id ```ruby= Rails.application.routes.draw do resources :restaurants resources :users, only: [:create] do collection do get :sign_up,controller: 'restaurants', action: 'new' # 可以在後方指定他要去的controller跟action end end ``` ### 建立 controller 、 action 跟 view 瀏覽器驗證 * 使用特定的 input 格式 * 常用:text_field, check_box, radio_button * email_field、password_field... * [官方文件說明 form helpers](https://guides.rubyonrails.org/form_helpers.html#other-helpers-of-interest) ```erb= <h1>註冊帳號</h1> <%= form_for(@user) do |form| %> <div class="field"> <%= form.label :email %> <%= form.email_field :email %> </div> <div class="field"> <%= form.label :password %> <%= form.password_field :password %> </div> <div class="field"> <%= form.label :password_confirmation %> <%= form.password_field :password_confirmation %> </div> <%= form.submit %> <% end %> ``` 因為沒有預設 password_confirmation 的欄位,所以在user model 裡建立 `attr_writer :password_confirmation` 讓他有個虛擬欄位可以放 此時如果要驗證是否相同要用 if ..... 如果在 model 裡有用 `confirmation: true` 的話,框架會自動生成 attr_writer,所以可以拿掉上面的用法,框架也會直接驗證是否相同 ### 資料驗證 [官方文件 關於驗證](https://guides.rubyonrails.org/active_record_validations.html) ruby regular expression: [https://rubular.com/](https://rubular.com/) 如果用 confirmation 的話,欄位名字也要是xxx_confirmation 使用 format => 驗證是否遵循特定格式輸入 ```ruby= validates :email, presence: true, uniqueness: true, format: { with: /.+\@.+\..+/ } #正規表達式(regular expression) validates :password, presence: true, confirmation: true ``` [0408 驗證錯誤訊息筆記](https://hackmd.io/-fgcjJmnRPioD8T76JfafA?view#%E6%9F%A5%E7%9C%8B%E9%8C%AF%E8%AA%A4%E8%A8%8A%E6%81%AF) ### callback 或 lifecycle ```ruby= before_save :aaa before_create :bbb ``` [官方文件說明callback](https://guides.rubyonrails.org/active_record_callbacks.html#available-callbacks) ### 密碼加密 - 使用 ruby 內建方法:[用 ruby 執行 SHA1 加密](https://ruby-doc.org/stdlib-2.4.0/libdoc/digest/rdoc/Digest/SHA1.html) - 在 model 中直接設定 ```ruby= before_create :encrypt_password private def encrypt_password self.password = Digest::SHA1.hexdigest(self.password) end ``` - 放在 before_create 因為只在建立時加密 如果放在 before_save 會在每次更動資料的時候都加密 => 造成已加密的密碼再次被加密 ```ruby= self.password = Digest::SHA1.hexdigest(self.password) # 兩邊都可以用 self.password = Digest::SHA1.hexdigest(password) self.password=(Digest::SHA1.hexdigest(password)) #上下相等 # 因為也是呼叫實體方法 password password = Digest::SHA1.hexdigest(password) # 不會出錯,但僅只是指定區域變數 ``` - 在密碼裡"加鹽" - 如果直接加密會很容易被猜到是用哪種加密法,進而直接解密 => 用密碼學中"加鹽巴"的方法增加安全性 [WIKI 說明](https://zh.wikipedia.org/wiki/%E7%9B%90_(%E5%AF%86%E7%A0%81%E5%AD%A6)) ```ruby= private def encrypt_password self.password = Digest::SHA1.hexdigest(salted_pwd) end def salted_pwd "123#{self.password}xx" # => 加密前在使用者指定的密碼前後加入 123 跟 xx 表示在前後位置插入特定的字串 end ``` ## 使用者登入介面 ### 建立 route (在這邊利用另一種方法示範) ```ruby= resources :sessions, path:'users', only: [] do # 加 path 改變路徑 collection do get :sign_in, action: 'new' # 登入頁面 post :sign_in, action: 'create' # 登入動作 end end ``` 如果直接在 resources 設定 only: :create 跟 :destroy,會有兩個重複路徑,所以改成自己刻路徑 ### 建立 sessions_controller ```ruby= def new @user = User.new end def create # email = user_params[:email] # password = user_params[:password] if User.login(user_params) # 發號碼牌 # 轉去首頁 redirect_to root_path else redirect_to sign_in_sessions_path end # 查有無帳號 / 密碼 # 轉址 / 重登 end private def user_params params.require(:user).permit(:email, :password) end ``` ### 修改 User 的 Model ==待補充== - 因為在 controller 裡面設定要 follow User Model,所以才去 User 的 Model ```ruby= def self.login(params) email = params[:email] password = params[:password] encrypted_password = Digest::SHA1.hexdigest("123#{password}xx") find_by(email: email, password: encrypted_password) end ``` - 簡化程式碼 - 把加密跟灑鹽兩個動作分開 - ==有錯誤喔喔喔,待龍哥講解== ```ruby= def self.login(params) email = params[:email] password = params[:password] salted_password = salted(password) encrypted_password = encrypted(salted_password) find_by(email: email, password: encrypted_password) end private def encrypt_password salted_pwd = salted(password) self.password = encrypt(salted_pwd) end def encrypt(password) Digest::SHA1.hexdigest(password) end def salted(password) "123#{password}xx" end ``` ## 題外話 ### 拿到新專案時 1. git clone 專案名 ( 將專案資料夾下載到本地端 ) 2. cd 專案名 ( 移動到專案資料夾) 3. 確認 `README.md` ( 確認說明檔及有無需要先下載的套件 ) 4. `$ bundle install` ( install gem,根據專案有無gemfile而選擇安裝 ) 5. `$ yarn install` ( install node_modules ) 6. `$ rails db:migrate` ( 沒做會噴錯誤訊息pending叫你去 `db:migrate` ) - 噴錯的話也可以去看 config 裡有沒有 .sample 檔案 8. `$ rails s` ( 開啟伺服器 ) > * bundle install:把想要使用的套件放在 Gemfile 裡,接者輸入指令 bundle install 即可使用 > * 只要在 schema 裡面有欄位,框架就會幫你產出 attr_writer 跟 attr_reader --- ### self 使用 ```ruby= class Cat def Cat.aa end def self.aa # 等於 Cat.aa end def cc p self # 等於實體本身 (下面例子裡是 kitty) end end kitty = Cat.new kitty.cc ``` [嘗試說明 self 用法的文章](https://www.rubyguides.com/2020/04/self-in-ruby/) ```ruby= class Cat def Cat.aa end def self.aa # 等於 Cat.aa end def xx self.yy yy end def yy end def cc p self # 等於實體本身 (下面例子裡是 kitty) end end kitty = Cat.new kitty.cc ``` --- ### session vs cookie * 登入成功的當得到一個cookie, 存在瀏覽器裡, session 也會得到一個cookie * cookie可以跟session( 後端 )比對資訊 > Both **Functional programming** and **object-oriented programming** uses a different method for storing and manipulating the data. In **functional programming**, data cannot be stored in objects and it can only be transformed by creating functions. In **object-oriented programming**, data is stored in objects.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully