# Ruby(10/14) 五倍紅寶石(第八屆共筆)(2021/8/17) ## n + 1 在迴圈裡面做查詢 用迴圈跑,每跑一次都要查一次 一開始用 each 也會查一次 - 如何解決: - Rails includes(:user) - DB in 語法 可以轉換成一筆查詢 ## 建立model - :belongs_to 資料類型 只會幫你加 belongs_to has_many、has_one 要自己加 :index 資料類型 會在 Rails 內增加 add_index,並且指定 INT 型態 ## Route:shallow nesting 淺的槽狀(專案必用) 路徑上的差別,看起來比較不囉唆 看3號 comment 不用從2號 note 裡面找 因為index new create 的路徑沒有 id 其他的有,才需要這樣分 ![](https://i.imgur.com/eEkbkdf.png) - 原效果: ```rb resources :notes do resources :comments, only: [:index, :new, :create] end resources :comments, only: [:show, :edit, :update, :destroy] ``` - evernote 專案: ```rb resources :notes do # shallow nesting resources :comments, shallow: true, except: [:new, :edit, :update] end ``` - 以上寫法會自動變成以下效果: ```rb resources :notes do resources :comments, only: [:index, :create] end resources :comments, only: [:show, :destroy] ``` - 印所有 params 方法: render html: params ## session HTTP是一種無狀態的通訊協定,在這邊做任何事情後,跳到其他頁面再回來並不會保留資訊。 可以透過Params 或 Cookies 讓瀏覽器能夠在切換頁面時記住資訊, Rails 提供了所謂的 Session 可以更方便的操作,**用來記住登入的狀態、記住使用者購物車的內容等等**。 發 session(cookie) = 一種認證機制 拿到 cookie 不等於登入 登入 = 拿到 session 而且可以做裡面的事 登入成功會發令牌(cookie)給你,瀏覽器會留一個 session 做比對,看是否正確 會有時間性,如果過期或是對方 server 重新啟動都要再重新登入 Session 是一個實體方法,用法跟 Hash 很像。 可以把 Session 當作是 Hash,把 user.id這個值存到屬性 thankyou9527,取名隨意。 ```rb session[:thankyou9527] = user.id ``` ## scope 商業邏輯,以後如果要改需求改這邊就好 ```rb # 定義類別方法:假刪除,刪掉的檔案只是讓他看不見而已 # 用 where 過濾,deleted_at 預設 nil,沒點刪除的都是 nil def self.available where(deleted_at: nil) end scope :available ``` ### default_scope 所有的搜尋都會加上後面的條件, 預設欄位值是deleted_at: nil的都看得到 可以用 unscope 把他去掉 ```rb # 一般的 scope 還是要在類別後面加這個方法名稱,default不用 => 不用寫 available default_scope { where(deleted_at: nil) } ``` ### paranoia [paranoia](https://github.com/rubysherpas/paranoia) 放在 Note Model 裡面 直接用 acts_as_paranoid 內建套件取代 default_scope { where(deleted_at: nil) } 使用前先在 Gemfile 寫入 gem "paranoia", "~> 2.2" 接著輸入 bundle install,如果沒反應就重開 server ## AJAX(可查查看) 非同步JavaScript,演給使用者看 用 XHR 送資料 render js:"alert:123" 將字串用 js 格式執行它 格式只能是瀏覽器有支援的 controller沒用render會去找同名檔案 js.erb 只有在Rails適用 正常是不行的,不是業界標準 ## 多對多關聯(專案必用) 用起來像多對多,但事實上是透過第三方(Join Table)約診簿串連兩者 - g model Bookmark belongs_to… 建立第三方 table ## JS - insertAdjacentHTML 在子層最前面新增物件 ### form helper - form_for (model):建立model - form_tag:建立一般表單 - form_with:都可以 兩個都可以當 有參數就用form_for,沒有就用form_tag 以前第三個參數預設 local:false = remote:true rails 6.1 後預設 local:true,要改成 local:false 才能用 Ajax 傳送資料 - form_with(model: @user) - form_with() - fetch: 使用非同步,會去 Web API 排隊,預設用get去抓東西 fetch 回傳 promise 用 then 來做事,繼續往下做 用 json 函式解讀,會回傳 promise 可以再繼續用 then 做事 resp 是 fetch 的結果 data 是 then 的結果 回傳 username ```js fetch("https://jsonplaceholder.typicode.com/users") .then((resp) => resp.json()) // Promise .then((data) => { for (let { username } of data) { console.log(username); } // data.forEach((d) => { // console.log(d.username); // }); }); ``` ### 解構 destructing (常見) let {} let [x,y,z] = list ### postman 取外部資訊(政府平台) 會遇到問題 => 搜尋關鍵字:CORS ## evernote 專案 ### 留言功能 1. 新增 Comment model rails g model Comment user:belongs_to content:text deleted_at:datetime:index note:belongs_to 2. 新增關聯 note.rb ```rb has_many :comments ``` 3. 建立 show畫面 show.html.erb ```rb <div class="note-heading"> <h1>筆記: <%= @note.title %></h1> <a href="#" id="favorite_btn"> <div class="favorite_icon"></div> </a> </div> <%= @note.content %> <%# remote 用非同步方式送資料,local: false = remote: true %> <%= form_with(model: @comment, url: note_comments_path(@note), local: false) do |f| %> <%= f.text_area :content %> <%= f.submit "新增留言" %> <% end %> <ul class= "comments"> <% @comments.each do |comment| %> <li><%= comment.content %></li> <% end %> </ul> ``` 4. 運用 has_many 的方法創造物件 notes_controller.rb ```rb def show # comments 由來: note model has_many comments @note = Note.find(params[:id]) @comment = @note.comments.new @comments = @note.comments.order(id: :desc) end ``` 5. shallow nesting routes.rb ```rb resources :notes do # shallow nesting # 做出 /comments/3/edit 效果 resources :comments, shallow: true, except: [:new, :edit, :update] end ``` 6. 新增 comments_controller 內容 comments_controller.rb ```rb before_action :check_login! before_action :find_user_note def create @note.comments.new(comment_params) # @note.user = current_user if @note.save @content = comment_params[:content] else redirect_to "/" end end private def comment_params params.require(:comment) .permit(:content) # comment 的 model 有寫兩個 belongs_to 所以這邊也要兩個欄位才能正常送出 # 語法糖衣 原本:應該寫在前面,merge 的參數要是 hash # 用 merge 把 user_id 加進去,讓他變兩個欄位 # merge 只會回傳我添加的東西(必須是 hash),不會改變我原始物件的內容 .merge(user_id: current_user.id) end # 把current user 改掉,讓每個人都可以看 def find_user_note @note = Note.find(params[:note_id]) end ``` 7. 不能編輯刪除別人文章 notes_controller.rb ```rb def find_user_note # 從個人角度去找文章,只有目前使用者能找 @note = current_user.notes.find(params[:id]) end ``` ### 註冊、登入、登出功能 rails g model Bookmark user:belongs_to note:belongs_to routes.rb ```rb # 路徑: GET /users/sign_up 註冊表單 # 路徑: GET /users/sign_in 登入表單 # collection 追加路徑,方法 :方法名稱 resources :users, only: [:create] do collection do get :sign_up get :sign_in end end # post :sign_in 拉出去做 # 登入跟註冊是兩件事情,註冊是一般的 CRUD,但登入會用到 sessions,所以把他獨立拉出來寫 # 寫入 sessions = 登入,用 as 換 path 名字,看起來比較乾淨 # 刪除 sessions = 登出 post "/users/sign_in", to: "sessions#create", as: "login" delete "/users", to: "sessions#destroy", as: "logout" ``` 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 ``` sessions_controller.rb ```rb # 把 user.id 存入 session id 來找到該位 user = 登入 def create session[:thankyou9527] = user.id end # 把 user.id 變成 nil 讓他找不到 user = 登出 def destroy session[:thankyou9527] = nil end ``` - Digest(摘要) 將數據轉換成 Hash 值,讓他不易被解密 user.rb ```rb # 使用 Digest 方法要先 require 進來 class User < ApplicationRecord # 可以用 attr accessor 騙他,實際上沒有這個欄位(不建議) # attr_accessor: :password_confirmation # 不要在資料庫存明碼 # confirmation = 確認兩個欄位值是一樣的, uniqueness = 唯一值, # 兩個都是驗證器 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 # has_many 會動態增加四種方法,可隨便取名,但最後要幫他指定 model(source: :note) 不然會找錯 # notes 查詢 # noets= 設定 # build 需要使用.save才會寫進資料庫 # create 會直接寫進資料庫裡 # https://rails.ruby.tw/association_basics.html#has-many-%E9%97%9C%E8%81%AF has_many :notes has_many :bookmarks has_many :favorite_notes, through: :bookmarks, source: :note # 加密方法: # MD5 已被破解 # SHA 比較安全 # 密碼只能被加密一次,兩次以上會看不懂 require "digest" def encrypt_password # Digest::MD5.hexdigest(pw) # salting:在密碼裡面加東西,讓他比較複雜 salted_pw = "xyz#{self.password}827128#{self.password}82-12j23h" self.password = Digest::SHA1.hexdigest(salted_pw) end end ``` show.html.erb ```rb <%# remote 用非同步方式送資料,local: false = remote: true %> <%= form_with(model: @comment, url: note_comments_path(@note), local: false) do |f| %> <%= f.text_area :content %> <%= f.submit "新增留言" %> <% end %> ``` application.html.erb ```rb <%# 如果要寫在別的地方要寫好路徑讓他找,建shared資料夾%> <%# 用render 去抓另外一個公版到這邊呈現,看起來比較不那麼亂%> <%= render "shared/navbar" %> ``` _navbar.html.erb ```rb <%# 共用的通常會創一個 shared 資料夾來放 %> <nav> <% if user_signed_in? %> <%# link_to 方法預設 GET,所以要改成 delete %> <%= link_to "登出", logout_path, method: "delete" %> | <%= current_user.email %> <% else %> <%#使用者登入跟沒登入會出現不同資訊%> <%= link_to "登入", sign_in_users_path %> | <%= link_to "註冊", sign_up_users_path %> <% end %> </nav> ``` create.js.erb ```rb <%# 用 let 或 const 會造成重複宣告,沒辦法送出多筆,改用 var %> <%# 拿到show.html.erb 的 .comments class,並清空 value%> var comment_area = document.querySelector(".comments").value = "" var content = "<li><%= @content</li>" <%# 把內容加在 ul 的 li 裡面,塞在最前面%> comment_area.insertAdjacentHTML('afterbegin', content) <%# 或是不要宣告,直接寫在一起 %> document.querySelector("#comment_content").value = "" document.querySelector(".comments") .insertAdjacentHTML('afterbegin', "<li><%= @content %></li>") ``` --- ###### tags: `Ruby` `Rails`