0%

  1. 因為devise預設都是吃Devise controller(如Devise::RegistrationsController),所以如果要更改設定,必須先建立另一個controller蓋過原始設定。

    1
    2
    $ rails generate devise:controllers [scope]
    #這邊的scope我使用users

    devise會幫你建立八個controller,如果不想那麼多,可以用以下指令替代

    1
    2
    $ rails generate devise:controllers users -c=sessions
    #只建立sessions這個controller
  2. 到routes.rb這邊放入這段code,告訴router使用我們建立的controller

    1
    2
    devise_for :users, controllers: { registrations: 'registrations' }
    # 其他controller如需要也可比照辦理
  3. 在registration.controller底下,放入這段code

    1
    2
    3
    def update_resource(resource, params)
    resource.update_without_password(params)
    end
  4. 在application_controller.rb裡面,放入這段code,讓我們新建的欄位,可以被params抓到並傳送

    1
    2
    3
    4
    def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
    devise_parameter_sanitizer.permit(:account_update, keys: [:name])
    end

    如此一來,就可以在不需要輸入密碼的情況下更改資料。

在使用devise這個gem的時候,雖然它已經幫我們預設做了很多事情,但總是有我們需要客製化的時候,這時候我們可以透過以下步驟來達到目的:

  1. 建立一個 migration,在table新增需要的欄位

    1
    2
    3
    4
    5
    6
    class AddColumnToUsers < ActiveRecord::Migration[6.0]
    def change
    add_column :users, :name, :string
    add_column :users, :birthday, :datetime
    end
    end
  2. rails db:migrate

  3. 確認schema.rb裡面已經加入欄位

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "name"
    t.datetime "birthday"
    t.string "confirmation_token"
    t.datetime "confirmed_at"
    t.datetime "confirmation_sent_at"
    t.string "unconfirmed_email"
    t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
    end
  4. 參考devise官網的指示,讓新欄位的parameters允許通過並打開before_action的註解https://github.com/heartcombo/devise#strong-parameters

1
2
3
4
5
6
7
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]

def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
devise_parameter_sanitizer.permit(:account_update, keys: [:name])
end
  1. 最後回到頁面將新增欄位show出來

進入目錄

1
2
3
4
$ cd .   #目錄原地
$ cd .. #回目錄的上一層
$ cd - #回上一動作的目錄層
$ pwd #確認當前目錄所在層

更改檔案權限

1
2
3
4
5
6
7
8
9
$ chmod ugo+r file.test # 幫所有人開讀取權限
$ chmod 777 file.test # 幫file.test這個檔案權限全開

# 檔案前面顯示的rwx r-x r-x 分別是檔案擁有者、群組、其他人的權限
# r 表示可讀取,w 表示可寫入,x 表示可執行
# 可使用二進位法 r=4,w=2,x=1
# 若要rwx屬性則4+2+1=7;
# 若要rw-屬性則4+2=6;
# 若要r-x屬性則4+1=7。

查看檔案清單

1
2
$ ls -l  #顯示這個目錄內的物件 
$ ls -al #顯示這個目錄內包含隱藏檔的物件

刪除檔案

1
2
3
4
5
6
$ rm  a    # 移除 a 檔案
$ rm -rf b # 強制移除b資料夾(慎用)
# "f"orce = 強制
# "r"ecursive = 層遞
# 空格代表 and
$ rm file* # 刪除以 file 開頭為檔名的所有檔案

檔案建立/更改

1
2
3
4
5
6
7
$ mv a b               #將 a 檔名改成 b
$ cp a b #將 a 檔複製後命名成 b
$ cat a #查 a 檔案內容
$ echo "b" >file.test #將 "b" 寫進file.test
$ touch a #新增 a 檔案
$ touch abc/def #在 abc 底下新增 def 檔案
$ vim a #更改 a 檔案內容

資料夾建立

1
$ mkdir abc        建立 abc 資料夾

整理終端機頁面

1
2
3
4
5
$ ctrl + w  # 刪除一段字
$ ctrl + u # 整行刪除
$ ctrl + l # 清理排面
$ ctrl + r # 查詢曾下過的指令
$ cmd + k # 全部清除,往上捲也找不到紀錄

與String(字串)有何不同?

1. string的內容可以變,symbol不行

相似之處在於,他們都可以使用字元方法如.length及.upcase等,也都可以使用中括號 + 索引來取得其中某個字元,但如果想修改symbol的內容,會發生語法錯誤,因為 Symbol 類別並沒有 []= 這個方法。

1
2
3
4
:lakers.length => 6
:lakers.capitalize[0] => "L"
:lakers[0] = "k"
undefined method `[]=' for :lakers:Symbol

2. symbol的提取效率比string好

上面提到,相同的string對ruby來說是不同物件,而相同的symbol則對應到記憶體中的相同物件。不同symbol 在做比較的時候,是直接比對這兩顆物件的 object_id是否相同,而字串比較則是一個字母一個字母逐一比對。所以在效率上來說,string在做比較的時間複雜度會隨著字母的數量而增加,但 Symbol 因為只比較是不是同一個物件,所以它的複雜度是不變的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
5.times do puts "lakers".object_id
end
=>47397526282660
47397526282360
47397526285620
47397526212580
47397526212520
5.times do puts :lakers.object_id
end
=>1031708
1031708
1031708
1031708
1031708

3. string跟symbol間可以互相轉換

  • string可透過.to_sym及.intern轉換成symbol。
1
2
3
4
"lakers".to_sym
=> :lakers
"lakers".intern
=> :lakers
  • symbol則可透過.to_s及id2name轉換成string。
1
2
3
4
:lakers.to_s
=> "lakers"
:lakers.id2name
=> "lakers"

4. 如果把 String 給”冰凍”(freeze)起來,它便不可修改,object_id 也會是同樣的。

1
2
3
4
5
6
3.times do   
puts "hello".freeze.object_id
end
# => 70314415546380
# => 70314415546380
# => 70314415546380

Symbol的使用時機?

Strings are used to work with data.
Symbols are identifiers.
You should use symbols as names or labels for things (like methods) &
use strings when you care more about the data (individual characters).

當我們需要內容是不可變動時,可使用symbol,因為 Symbol具有不可變(immutable),像標籤一樣的特性,以及它的查找、比較的速度比字串還快,它很適合用來當 Hash 的 Key。

1
hash = {a: 1, b: 2, c: 3}

另外有的方法的參數是用字串,有的是用 Symbol,有的是兩種都能用,那該怎麼知道該用哪一種?可參考ruby官方api。

參考資料:

Ruby 2.7.2

Ruby 語法放大鏡之「有的變數變前面有一個冒號(例如 :name),是什麼意思?」 | 高見龍

What Are Ruby Symbols & How Do They Work? - RubyGuides

[Ruby] Symbol(符號)

當我們在rails專案裡面執行關聯性的查詢時,時常會出現N+1 query的狀況,造成效能浪費。舉個例子,在一些網站上我們常可以看到使用者(user)可以針對文章(post)有多個評論(comment),而我們想在頁面上列出所有comment以及每個comment的user是誰時,就會出現N + 1 query:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#user model
class User < ApplicationRecord
has_many :comments
end

#comment model
class Comment < ApplicationRecord
belongs_to :user
end

#post model
class Post < ApplicationRecord
has_many :comments
end

#posts_controller
def show
@comment = Comment.new
@comments = @post.comments
end

#post的show頁面,列出此post所有comments,並且標示user
<ul>
<% @comments.each do |comment| %>
<li>
[<%= comment.user.nickname %>]
<%= comment.content %>
</li>
</ul>

只要每次我們刷新show頁面時,除了撈出所有comments以外,還會針對每一個comment去跑迴圈,到資料庫撈它的user,而所謂的N指的就是這個動作。

使用includes方法解決(eager loading)

1
2
3
4
5
6
7
#posts_controller
def show
@comment = Comment.new
@comments = @post.comments.includes(:user)
end

#查詢post的所有comment時,把user資料一併關聯進來,就可以先分析並一次把user資料撈出來。

從畫面可以看到從原本的N + 1的查詢,變成1 + 1了,而SQL語法則使用IN()取代原本一筆筆用=去取資料,一次所有相關user資料撈出來。

網路上其實也有人針對rails的N + 1問題做成套件,當發生此問題就會跳出提醒,相關資訊和安裝方式可參考:https://github.com/flyerhzm/bullet

Block的寫法有哪些?

  • do..end
1
2
3
5.times do |i|
puts i
end
  • 花括號{ }
1
2
3
5.times { |i|
puts i
}

Note: block裡面可帶參數進去,例子中是|i|,但這個i只在block裡面作用,類似JS匿名function的參數。

兩種寫法有何不同?

一般我們遇到比較可以一行搞定的,通常就會選擇花括號,而比較負責的邏輯判斷時,則使用do..end寫法,但這兩種方法還是有一些差異存在,以下舉一個簡單的例子給大家看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
array = [1, 3, 5, 7]

p array.map{ |a| a * a }

p array.map do
|a| a * a
end

ruby常省略小括號,原型其實是:
p(array.map{ |a| a * a }) # 印出 [1, 9, 25, 49]

p(array.map) do #印出 <Enumerator: [1, 3, 5, 7]:map>
|a| a * a
end

上面兩種結果顯示do..end這個寫法的結合力較差,在這個例子中的array.map優先跟p結合,而不是後面的block,所以後面的block無法被順利執行。

Block不是物件,無法獨立使用

1
2
3
{ puts "hello" } #出現語法錯誤

do puts "hello" end #語法錯誤

使用Proc、lambda將block物件化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#使用Proc
times_five = Proc.new { |x| x * 5 } #後面的block變成一個物件,並且可以傳參數進去

p times_five.call(3) #印出15
p times_five. === 3 #印出15
p times_five.(3) #印出15
p times_five[3] #印出15
p times_five.yield(3) #印出15

#使用lambda
times_five = lambda {|x| x * 5} #後面不需要加new方法
times_five = -> (x) { x * 5} #lambda的另一種寫法

p times_five.call(3) #印出15
p times_five. === 3 #印出15
p times_five.(3) #印出15
p times_five[3] #印出15
p times_five.yield(3) #印出15

#兩種方式使用上的不同
times_five = Proc.new { |x| x * 5 } #執行像是block
p times_five.call(1, 3, 5) #正常執行,但只帶第一個引數,印出5。如果絕對值裡面有兩個參數,就可以帶兩個引數進去

times_five = lambda {|x| x * 5} #執行像是方法,參數和引數數量必須相同
p times_five.call(1, 3, 5) #wrong number of arguments (given 3, expected 1) (引數個數錯誤)

Block是如何被執行的?

可使用yield將控制權交給block執行內容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def greeting
puts "hello, phil"
yield
puts "let's go lakers"
end

greeting{
puts "have you watched NBA final today?"
}

puts "2 more wins to go!"

#印出
hello, phil
have you watched NBA final today?
let's go lakers
2 more wins to go!

應用block概念土炮手動.select

1
2
3
4
5
6
7
8
9
10
11
def my_select(list)
result = [] #製作空陣列
list.each do |n| #將list裡面的每個數字拋進來
result << n if yield(n) #將數字再給回下面block執行,如果成立,丟到result的陣列裡面
end
result #回傳結果
end

p my_select([1, 2, 3, 4, 5]) { |i| i.odd? }

#印出[1, 3, 5]

有時有多個類別都需要使用同個方法,這時候可以定義一個模組,讓每個類別可以引入取用。

模組與繼承的不同之處:

  • 模組沒有繼承的功能:
1
2
3
4
module Flyable < other module
end

#無法繼承
  • 模組不能被實體化:
1
2
3
4
5
6
module Flyable
end

Dog = Flyable.new

#undefined method `new' for Flyable:Module (NoMethodErro

模組引入有兩種寫法:

  1. 在類別內include模組,此類別製造的實體可使用模組的方法(實體方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module Invisible
def woolala
puts "nobody can see #{@name} now!"
end
end

class Human
include Invisible
def initialize(name)
@name = name
end
end

Phil = Human.new("Phil")
Phil.woolala

#印出 nobody can see Phil now!
  1. 在類別內extend模組,此類別可直接使用方法(類別方法),但其製造的實體不可使用此方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module Flyable
def fly
puts 'I can Fly!'
end
end

class Cat
extend Flyable
end

Cat.fly
#印出 I can Fly!

Phil = Cat.new
Phil.fly
#undefined method `fly' for #<Cat:0x0000563c17581b38> (NoMethodError)

如有重複的類別名稱,可用不同模組區隔,製作實體時,需用” :: “來鎖定欲呼叫的類別。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module A
module Z
class Dog
end
end
end

module B
class Dog
end
end

Ryan = A::Z::Dog.new #必須一層層呼叫到類別
Will = B::Dog.new

冒號的位置在Ruby代表不同意思

  • 冒號在左邊,代表符號
1
has_name :name
  • 冒號在右邊,代表hash裡面的key
1
{name: "Phil", age: 32}
  • 冒號在中間,前者是key,後者是符號,中間用空格隔開
1
{direction: :up, power: 100}
  • 冒號在中間,而且沒有空格,代表呼叫模組裡面的類別
1
2
class User < ActiveRecord::Base #User繼承ActiveRecord模組裡面的Base類別
end