Kurt Hsu's blog

The Rails developer in taiwan.


  • 首頁

  • 標籤

  • 分類

  • 歸檔

[Rails] I18n default scope

發表於 2020-05-26 更新於 2020-05-28 分類於 Rails , i18n

在挖 deivse set_flash_message code 的時候研究了很久, 把一些心得記錄下來
這篇就不把 locale 檔案一一打出來了, 只記錄一下最終字串結果, 並且架設目前語系是 zh

基本使用和特性

1
2
3
4
5
6
7
8
I18n.t(:hello)
I18n.t('hello')
I18n.t(:hello, :world)
I18n.t([:hello, :world])
I18n.t(['hello', 'world'])
# => zh.hello
I18n.t('hello.world')
# => zh.hello.world

scope

scope 像是 namespace 的感覺, 要注意是加在最前面

1
2
3
4
5
6
I18n.t(:cat, scope: :super)
I18n.t(:cat, scope: 'super')
# => zh.super.cat
I18n.t(:cat, scope: [:super, :power])
I18n.t(:cat, scope: [:super, 'power'])
# => zh.super.power.cat

default

default 就是當找不到 i18n 翻譯時回傳的東西, 如果給予 array 則找到東西為止

1
2
3
4
5
6
I18n.t(:man, default: 'I am Iron man')
# => 如果沒有找到 zh.man 的翻譯就回傳字串 'I am Iron man'
I18n.t(:man, default: [:super, :hero, 'I am Iron man'])
# => 依順序 zh.man > zh.super > zh.hero 的翻譯都沒有找到的話就回傳字串 'I am Iron man'
I18n.t(:man, default: [:super, :hero])
# => 同上依順序的翻譯都沒有找到的話就回傳字串 'translation missing: zh.man'

但要注意的是如果翻譯是有階層的話會回傳整個 hash, 例如:

1
2
3
4
zh:
man:
super:
hansome: '我是帥哥'

這時 I18n 的動作為:

1
2
I18n.t(:man, default: [:super, :hansome])
# => {:hansome=>'我是帥哥'}

因為是 hash 不是 nil 就會整包回傳過來了

scope 搭配 default

default 也會吃到 scope namespace

1
2
I18n.t(:man, scope: [:super, :iron], default: [:woman, 'I am Iron man'])
# => 依順序 zh.super.iron.man > zh.super.iron.woman 的翻譯都沒有找到的話就回傳字串 'I am Iron man'

小結

我還是覺得非必要不要寫太多的 scope 和 default 其實挺通靈的, 如果沒有把 devise 的 find_message binding 看裡面發生了什麼事情其實挺難懂的, 還是用最基本的寫法比較好收尋和維護

I18n 系列:

[Rails] I18n 基本使用] [Rails] I18n 語系設定]

[Rails]Devise confirmable

發表於 2020-05-26 分類於 Rails , Gem

記錄一下自己實作 devise confirmable 基本功能
將會做到:

  1. 註冊完之後發驗證信, 並且驗證完後才能正常登入
  2. 客製化 devise controller
  3. 提前阻擋 warden 這個 gem 的 error respond 處理

參考環境

記錄一下自己的版本, 也順便檢查有無安裝到相關 gem:

Gemfile
1
2
3
4
5
6
7
ruby '2.6.3'

gem 'rails', '~> 6.0.2', '>= 6.0.2.2'

group :development do
gem 'letter_opener'
end

Gemfile.lock
1
2
3
4
5
6
7
8
  rails (6.0.2.2)
letter_opener (1.7.0)

RUBY VERSION
ruby 2.6.3p62

BUNDLED WITH
2.0.2

我的 model 是 User

model 新增 confirmable

migration 新增 confirmable 相關欄位

1
2
3
4
5
6
7
8
class AddConfirmableToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :confirmation_token, :string
add_column :users, :confirmed_at, :datetime
add_column :users, :confirmation_sent_at, :datetime
# add_column :users, :unconfirmed_email # Only if using reconfirmable
end
end

官方欄位解說和自己見解

  • confirmation_token - A unique random token

    只要發送驗證信該 User 會更新這個欄位, 驗證信上的網址會帶上這個 token 找到該 User 然後做驗證

  • confirmed_at - A timestamp when the user clicked the confirmation link

    該 User 有沒有驗證過是看這個欄位有沒有值

  • confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)

    可以設定 token 有效期限

  • unconfirmed_email - An email address copied from the email attr. After confirmation this value is copied to the email attr then cleared

    User 修改 E-mail 可以做重新認證, 這次沒有用到這個功能所以也沒新增這個欄位

更新 User model

user.rb
1
2
3
class User < ApplicationRecord
devise :confirmable
end

建立 controller 以及 更新 route

使用 devise 提供的指令建立 controller
$ rails generate devise:controllers users -c=confirmations
將為產生app/controllers/api/v1/users/confirmations_controller.rb, 且提示要更新 routes 的訊息如下:

config/routes.rb
1
2
3
4
5
Rails.application.routes.draw do
devise_for :users, controllers: {
sessions: 'users/sessions'
}
end

也可以自己規劃資料夾, 例如想把 devise 的 controller 都規劃到 API 底下的話, 把剛剛 devise 產生的資料夾移動到 API 資料夾結構底下, 並且更新 controller 和 routes

app/controllers/api/v1/users/confirmations_controller.rb
1
2
3
4
5
6
7
8
9
module Api
module V1
module Users
class ConfirmationsController < Devise::ConfirmationsController
# Lots of methods and code there...
end
end
end
end
config/routes.rb
1
2
3
4
5
6
7
Rails.application.routes.draw do
root to: 'page#landing'

devise_for :users, controllers: {
confirmations: 'api/v1/users/confirmations'
}
end

再來我要做的 confirmable 非常簡單, 以下幾點:

  1. 點信件連結登入後認證 User 並且登入導回首頁
  2. 認證過的 User 會提示已認證過並且登入導回首頁
  3. 其餘的錯誤由 flash 呈現並且導回首頁

最後 controller 只會用到 show 這個 method 所以 overwrite show 即可:

app/controllers/api/v1/users/confirmations_controller.rb
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
module Api
module V1
module Users
class ConfirmationsController < Devise::ConfirmationsController
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])

# Devise adds errors if fail confirm by token
if resource.errors.empty?
set_flash_message!(:notice, :confirmed)
sign_in(self.resource)
# Ignore other errors if already confirmed
elsif resource.confirmed?
set_flash_message!(:notice, 'errors.messages.already_confirmed')
sign_in(self.resource)
else
# Show devise's other oringin errors
errors = self.resource.errors.full_messages.join("\n")
set_flash_message!(:notice, errors)
end

redirect_to root_path
end
end
end
end
end

設定 letter_opener 做測試

最後 devise 在註冊成功的時候即會自動發郵件, 可以用 letter_opener 模擬打開認證信件然後做測試, Gemgile 安裝好之後做以下設定:

config/environments/development.rb
1
2
3
4
5
Rails.application.configure do
# ... lots of codes
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
end

重新打開 server 之後就可以註冊一個新帳號試試看剛剛的所有設定有沒有成功了

登入後註冊 console 會噴 302 error (warden 問題)

devise 不允許在登入後再做註冊的動作, 這時候會回傳 302 error 掉並且跳轉, 所以最簡單解是在自己的 registrations_controller.rb 加上 prepend_before_action 檢查是否已經登入:

your_path_to/registrations_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
class RegistrationsController < Devise::RegistrationsController
prepend_before_action :already_signed_in, only: [:create]
#...lots of codes
private

def already_signed_in
return if !current_user

# Or your can add a flash and redirect_to other place
render json: "Already signed in", status: 302
end
end

發生的原因是因為 Devise::RegistrationsController 會做一個 prepend_before_action :require_no_authentication, only: [:new, :create, :cancel] 的動作, 他被定義在 DeviseController, 然後執行到 warden.authenticate?(*args) 這段的時候會回傳 302 error 掉並且跳轉, 所以我又再加一個比他更前面的動作 prepend_before_action 去驗證是否已經登入並且做自己想要的流程

可以不用先驗證

如果想先給會員註冊完馬上使用一些不用一定要先驗證的功能可以在 devise config 新增:

config/initializers/devise.rb
1
2
3
Devise.setup do |config|
config.allow_unconfirmed_access_for = nil
end

這個參數可以設定天數, 例如設定
config.allow_unconfirmed_access_for = 2.days
意思是這兩天內會員還可以不用先驗證, 如果設定 nil 代表永遠都可以不用先驗證
而要用到需要驗證功能的時候可以在 controller 用 current_user.confirmed? 去做判斷

手動發送驗證信

預設註冊成功會有發送信件的行為, 如果想把這個行為改掉用手動發送的話就在 controller 裡執行:

1
current_user.send_confirmation_instructions

略過驗證/直接驗證

這邊是指直接讓該 user 通過驗證, 因為英文不好一開始我還以為在做剛剛提到的『可以不用先驗證』的事情, 兩個是完全不一樣的, 看以下範例, 假設我註冊了之後還沒驗證

1
2
3
4
5
6
some_user = User.last
puts some_user.confirmed? # => false
puts some_user.confirmed_at # => nil
some_user.skip_confirmation!
puts some_user.confirmed? # => true
puts some_user.confirmed_at # => some date time

小結

這篇主要是野生的(自己實作遇到問題自己解), 相信團隊裡資深的成員都有更完善更好的處理方法

我實作的是基本的 confirmable, 還有其他功能 reconfirmable 沒有做到, 不過官方慢慢挖應該都有 devise 算是還蠻好挖慢慢看就看得懂的 gem, 只是要注意一些 warden 的動作, 有時候他會直接回傳錯誤然後直接導向感覺很像通靈, 我是 monkey patch 一下才知道原來是他搞的鬼

簡單處理 warden 自己回傳錯誤然後直接導向的問題可以先下個關鍵字 devise CustomFailureApp, 基本的狀況和用法在另外寫一篇記錄

[Javascript]原生 JS functional default params

發表於 2020-05-25 分類於 JavaScript

想要實作出有 default params 的 functional
意思是希望能有這樣的效果

1
2
3
4
5
6
7
8
myFunction({
name: 'Kurt',
age: 30
})
//=>
//name is Kurt
//age is 30
//sex is male

需要使用 ES6 的語法, 先來理解

1
2
3
4
5
6
7
8
9
let hash = {
name: 'Kurt',
age: 30
}

// 我們可以直接拿 hash 裡面的值宣告變數
let { name, age } = hash
console.log(name)// => 'Kurt'
console.log(age) // => 30

回到一開始想做的範例, 搭配這個特性定義 default params 的 function 即可完成, 也可以寫一些基本 error condition 的狀況

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const myFunction = ({
name = 'Adam',
age = 10,
sex = 'male'
}={}) => {
if (typeof name !== 'string' ||
typeof age !== 'number' ||
typeof sex !== 'string') {
throw 'Params typo invalid'
}
console.log(name)
console.log(age)
console.log(sex)
}

myFunction({
name: 'Kurt',
age: 30
})
//=>
//name is Kurt
//age is 30
//sex is male

有幾個點注意:

  1. 不用照順序給參數
  2. 參數少給或多給都沒關係
  3. 多給的 params key 不存在會被省略
1
2
3
4
5
myFunction({ sex: 'female', somekey: 'bla' })
//=>
//name is Adam
//age is 10
//sex is female

[Vue2]vue form 搭配 vue-axios基本使用

發表於 2020-05-22 更新於 2020-05-29 分類於 Vue2

記錄一下自己如何管理 Vue file 的表單, 並且用 vue-axios 做 post, 不過這是野生(就是我自己想的)的方式, 請參考即可

先列出幾個主題可以直接收尋:

  1. 包裝 postData
  2. 如何處理 form errors
  3. 注意 axios callback 裡如何使用 i18n

下面是一個簡單的完整示範:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<template>
<form
@submit="postForm"
action="/somewhere"
method="post"
>
<div v-for="(value, key) in postData" :key="key" class="field">
<label class="label">{{ $t(key) }}</label>
<input
:name="postData[key]"
v-model="postData[key]"
:class="{ 'is-danger': errors[key] }"
>
<p v-if="errors[key]">
{{ $t('errors.' + errors[key]) }}
</p>
</div>
</form>
</template>

<script>
export default {
data: function () {
return {
postData: {
email: '',
password: '',
password_confirmation: ''
},
errors: {
email: '',
password: '',
password_confirmation: ''
}
}
},
methods: {
postForm: function (e) {
e.preventDefault()

this.validForm()
if (!this.hasErros()) {
this.axios.post('/somewhere', this.postData)
.then(response => {
let { status, data } = response

if (status === 200) {
alert(this.$root.$t(data))
} else {
alert(this.$root.$t(data))
}
})
.catch(error => {
let { status, data } = error.response

if (status === 400) {
alert(this.$root.$t(data))
} else {
alert(this.$root.$t(data))
}
}
}
},
validForm: function () {
// Do validations...
},
hasErros: function () {
let bool = false
Object.keys(this.errors).forEach(key => {
if (!!this.errors[key]) { bool = true }
})
return bool
}
}
}
<script>

1. 包裝 postData

像在 data 做以下整理:

1
2
3
4
5
6
7
8
9
data: function () {
return {
postData: {
email: '',
password: '',
password_confirmation: ''
}
}
}

第一個好處是可以直接用一行做 post
this.axios.post('/somewhere', this.postData)

第二個好處是可以在 template v-for 直接把所有的 input 印出來

1
2
3
4
5
6
7
8
<div v-for="(value, key) in postData" :key="key" class="field">
<label class="label">{{ $t(key) }}</label>
<input
:name="postData[key]"
v-model="postData[key]"
:class="{ 'is-danger': errors[key] }"
>
</div>

如果要動態新增欄位則需要更新

1
this.$set(this.postData, key, value)

2. 如何處理 form errors

可以注意到我的 data 除了包含 postData 還有 errors, 不過這等於重複寫了一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data: function () {
return {
postData: {
email: '',
password: '',
password_confirmation: ''
},
errors: {
email: '',
password: '',
password_confirmation: ''
}
}
}

可以注意到我在 v-for 製造 input tags 的時候也都把錯誤訊息的 DOM 用 v-if 做了出來

1
2
3
4
5
6
<div v-for="(value, key) in postData" :key="key" class="field">
<input ..../>
<p v-if="errors[key]">
{{ $t('errors.' + errors[key]) }}
</p>
</div>

這樣也方便直接規劃 i18n, 例如 api 回傳的 error 訊息為:

1
2
3
{
email: 'invalid_email'
}

如果用 rails 專案自打字給規劃, 甚至可以直接整個 errors 原封不動的接過來更新 vue datas

1
2
3
4
5
6
7
8
9
10
this.axios.post('/somewhere', this.postData)
.then(response => {
this.closeModal()
})
.catch(error => {
let { status, data } = error.response
Object.keys(data).forEach(key => {
this.errors.user[key] = data[key].join('\n')
})
})

我只要設計 locale 如以下就吃得到

1
2
3
4
5
export default {
errors: {
invalid_email: '無效的 E-mail'
}
}

最後可以寫一個 method 去檢查 errors 有沒有任何錯誤回傳一個 boolean 值

1
2
3
4
5
6
7
hasErros: function () {
let bool = false
Object.keys(this.errors).forEach(key => {
if (!!this.errors[key]) { bool = true }
})
return bool
}

不過有幾個缺點:

  1. errors 基本上就是重新再寫一次 postData 的東西
  2. errors 不能先只給空 object {} 後來再慢慢新增, vue 會偵測不到

    也就是如果像下面這樣設計後來執行 this.errors.email = 'invalidatedEmail' vue 是吃不到的, 畫面不會更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    data: function () {
    return {
    postData: {
    email: '',
    password: '',
    password_confirmation: ''
    },
    errors: {}
    }
    }
  3. 如果寫 v-show 的話會因為一開始沒有錯誤而噴 error 找不到$t('errors.')的翻譯

  4. 動態新增或刪減 postData 可能都要對 errors 重複做一次一樣的操作

這是野生的方法XD
不過我目前的小專案是覺得這些缺點還沒有關係, 大專案如果有有經驗的前端應該會有更好的規劃

3. 注意 axios callback 裡如何使用 i18n

注意到 axios 的 callback 的 scope, 想用$t('trans.xxx')執行 i18n 的話要改成 this.$root.$t('trans.xxx')

參考來源:
首先感謝大學同學Hydra大大的一些指導
Hydra動態新增欄位的示範
How to fix “this is undefined” in Vue -> 可以了解一下什麼時候該不該用 arrow function

[Rails]rails console active_record hide culomns

發表於 2020-05-13 更新於 2020-05-14 分類於 Rails

突然很好奇 devise 如何在 rails console 裡面隱藏 columns, 例如在 schema 裡面 users 這個 table 明明有 encrypted_password 這個欄位為什麼 console 不會出現呢?

ActiveRecord::Relation#inspect

首先了解我們在 console 撈 ActiveRecord 為什麼會回傳以下的東西

1
2
$ User.first
#<User id: 1, email: "test@test.com", created_at: "2020-05-08 09:18:36", updated_at: "2020-05-08 09:18:36">

原來是在 ActiveRecord::Relation#inspect 這個 method 做事情

1
2
3
4
5
6
7
8
def inspect
subject = loaded? ? records : self
entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)

entries[10] = "..." if entries.size == 11

"#<#{self.class.name} [#{entries.join(', ')}]>"
end

而 devise 這個 gem 在 module Authenticatable 定義如下:

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
30
31
32
33
34
35
36
37
38
BLACKLIST_FOR_SERIALIZATION = [
:encrypted_password,:reset_password_token, :reset_password_sent_at,
:remember_created_at,
:sign_in_count,
:current_sign_in_at,
:last_sign_in_at,
:current_sign_in_ip,
:last_sign_in_ip,
:password_salt,
:confirmation_token,
:confirmed_at,
:confirmation_sent_at,
:remember_token,
:unconfirmed_email,
:failed_attempts,
:unlock_token,
:locked_at
]

def serializable_hash(options = nil)
options = options.try(:dup) || {}
options[:except] = Array(options[:except])

if options[:force_except]
options[:except].concat Array(options[:force_except])
else
options[:except].concat BLACKLIST_FOR_SERIALIZATION
end

super(options)
end

def inspect
inspection = serializable_hash.collect do |k,v|
"#{k}: #{respond_to?(:attribute_for_inspect) ? attribute_for_inspect(k) : v.inspect}"
end
"#<#{self.class} #{inspection.join(", ")}>"
end

當然可以在任一 model 重新定義他, 即使隨意修改另一個字串也依然可以對 User.last 這個 ActiveRecord object 正常操作, 這個只是在撈資料的時候回傳一個好讀的字串而已, 官方的解釋如下:
Returns the contents of the record as a nicely formatted string.

那如何看到全部 column 以及 value 呢

簡單的下 attributes methods 即可:

1
2
$ User.last.attributes
{"id"=>1, "email"=>"test@test.com", "encrypted_password"=>"xxxxx", "reset_password_token"=>nil, "reset_password_sent_at"=>nil, "remember_created_at"=>nil, "confirmation_token"=>"xxxxx", "confirmed_at"=>nil, "confirmation_sent_at"=>Fri, 08 May 2020 09:18:36 UTC +00:00, "unconfirmed_email"=>nil, "created_at"=>Fri, 08 May 2020 09:18:36 UTC +00:00, "updated_at"=>Fri, 08 May 2020 09:18:36 UTC +00:00}

只是要注意這個就是一個 hash 嘍!

只想在某一個 API 像這樣 hide 一些欄位呢?

我們可以用去除法(except)

1
User.first.serializable_hash({except: [:email, :id]}).to_json

只想取某幾個欄位的方法(only)

1
User.first.serializable_hash({only: [:email, :id]}).to_json

甚至可以取 nested 的欄位

1
2
# user has_many notes
User.first.serializable_hash(include: { notes: { only: 'title' }})

參考文章

Rails官方 for inspect
Rails官方 for serializable_hash
Stackoverflow - Rails 5 model objects not showing all Devise attributes
Stackoverflow - Rails & Devise: devise specific columns not showing up in rails console

[Rails]webpacker stylesheet_pack_tag 小坑紀錄

發表於 2020-04-22 分類於 Rails , Webpacker

這篇是記錄結果, 沒想到這個小坑花了我三四個小時才弄出來
難以追蹤的是我一直沒有注意到本來監聽 application.js 的動作已經被同名的檔案蓋過去

先說官方檔案架構:

1
2
3
4
5
6
7
8
app/javascript:
├── packs:
│ # only webpack entry files here
│ └── application.js
└── src:
│ └── application.css
└── images:
└── logo.svg

再來就是有一段官方敘述

1
You can then link the JavaScript pack in Rails views using the javascript_pack_tag helper. If you have styles imported in your pack file, you can link them by using stylesheet_pack_tag

其他沒有提及太多東西

跌坑開始

我本來想做的事情是在 rails 專案裡面一切的 js 和 css 都交由 webpacker 管理, 所以我的檔案結構如下:

1
2
3
4
5
6
app/javascript:
├── packs:
│ └── application.js
│ └── application.scss
└── styles:
└── some.scss

然後 application.css 去 import 所有 css, 最後在 html 下兩個 tag:

1
2
<%= stylesheet_pack_tag 'application' %>
<%= javascript_pack_tag 'application' %>

神奇的事情發生了, 我不論怎麼改 application.js, webpack-dev-server 都不會有反應了, 搞了很久才發現在資料夾 app/javascript/packs 底下原來檔案主名稱不能相同…, 本來 webpack-dev-server 監聽 application.js 的行為被相同檔案主名稱的 application.scss 蓋過去了…

development 解決方法很簡單

js 檔案去 require css 檔案就好不用多此一舉創造什麼 application.scss, 然後也會發現 html 不加 <%= stylesheet_pack_tag ‘application’ %> css 依然運行的很棒

那為什麼要出一個 stylesheet_pack_tag 然後也沒講得很清楚是幹嘛用的給我誤導RRR

extract_css

原來在 webpacker.yml 有一個 extract_css 設定 default 在 development 環境是 false, 然後在 production 環境是 true

extract_css 顧名思義是把 css 的東西提出來, 如果設定 true 的話會發現剛剛 require 的 css 沒用惹!

目前解決方法

extract_css 不管有沒有設定為 true, html 都把兩個 tag 帶上

1
2
<%= stylesheet_pack_tag 'application' %>
<%= javascript_pack_tag 'application' %>

檔案結構改為如下:

1
2
3
4
5
6
app/javascript:
├── packs:
│ └── application.js
└── styles:
└── application.scss
└── some.scss

js 依然 require application.scss

application.js
1
require("../styles/application.scss")

application.scss 當作主要 css 的載入檔案

application.scss
1
2
3
@import 'bulma';

@import './some.scss';

這樣既不會衝檔名, 也不會因為在 produciton 環境時 extract_css 為 true 而找不到 css 了

心得

不過最後的疑問是 extract_css false 有啥好處呢? 既然跟 production 行為有落差為何不同步就好QQ

然後 stylesheet_pack_tag 真的蠻少資訊的, 也不確定我理解的是否正確

[Node]安裝 Node Version Manager(NVM)

發表於 2020-04-21 更新於 2020-04-22 分類於 Node , NVM

mac 建議用 curl 安裝
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

這會自動下載 script 並執行它, 動作是把整個 repo clone 到 ~/.nvm 底下

並且新增 source lines 到這些檔案 ~/.bash_profile, ~/.zshrc, ~/.profile, or ~/.bashrc

可以看到下載完之後的所有訊息, 做了一些事情, 除了用了一些環境變數大部都是註解, 可以額外做哪些事情

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
=> Appending source string to /Users/Kurt/.zshrc
=> You currently have modules installed globally with `npm`. These will no
=> longer be linked to the active version of Node when you install a new node
DEFAULT_USER="Kurt Hsu"

# Path to your oh-my-zsh installation.
export ZSH=$HOME/.oh-my-zsh

# Set name of the theme to load.
# Look in ~/.oh-my-zsh/themes/
# Optionally, if you set this to "random", it'll load a random theme each
# time that oh-my-zsh is loaded.
ZSH_THEME="agnoster"

# Uncomment the following line to use case-sensitive completion.
# CASE_SENSITIVE="true"

# Uncomment the following line to use hyphen-insensitive completion. Case
# sensitive completion must be off. _ and - will be interchangeable.
# HYPHEN_INSENSITIVE="true"

# Uncomment the following line to disable bi-weekly auto-update checks.
# DISABLE_AUTO_UPDATE="true"

# Uncomment the following line to change how often to auto-update (in days).
# export UPDATE_ZSH_DAYS=13

# Uncomment the following line to disable colors in ls.
# DISABLE_LS_COLORS="true"

# Uncomment the following line to disable auto-setting terminal title.
# DISABLE_AUTO_TITLE="true"

# Uncomment the following line to enable command auto-correction.
# ENABLE_CORRECTION="true"

# Uncomment the following line to display red dots whilst waiting for completion.
# COMPLETION_WAITING_DOTS="true"

# Uncomment the following line if you want to disable marking untracked files
# under VCS as dirty. This makes repository status check for large repositories
# much, much faster.
# DISABLE_UNTRACKED_FILES_DIRTY="true"

# Uncomment the following line if you want to change the command execution time
# stamp shown in the history command output.
# The optional three formats: "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
# HIST_STAMPS="mm/dd/yyyy"

# Would you like to use another custom folder than $ZSH/custom?
# ZSH_CUSTOM=/path/to/new-custom-folder

# Which plugins would you like to load? (plugins can be found in ~/.oh-my-zsh/plugins/*)
# Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/
# Example format: plugins=(rails git textmate ruby lighthouse)
# Add wisely, as too many plugins slow down shell startup.
plugins=(git)

# User configuration

# export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
# export MANPATH="/usr/local/man:$MANPATH"

source $ZSH/oh-my-zsh.sh

# You may need to manually set your language environment
# export LANG=en_US.UTF-8

# Preferred editor for local and remote sessions
# if [[ -n $SSH_CONNECTION ]]; then
# export EDITOR='vim'
# else
# export EDITOR='mvim'
# fi
=> with `nvm`; and they may (depending on how you construct your `$PATH`)
=> override the binaries of modules installed with `nvm`:

/Users/Kurt/.nvm/versions/node/v10.16.0/lib
├── create-react-app@3.2.0

=> If you wish to uninstall them at a later point (or re-install them under your
=> `nvm` Nodes), you can remove them from the system Node as follows:

$ nvm use system
$ npm uninstall -g a_module

=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR="/Users/Kurt/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm

主要是最後這段

1
2
export NVM_DIR="/Users/Kurt/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"

source 擁有這段的檔案才會找到 nvm
但正常來講重開終端機就有了

可以嘗試有沒有安裝成功
$ nvm list

安裝想要的版本並且使用它
$ nvm install 8.11.2
$ nvm use 8.11.2

最後確認
$ nvm list
可以看到有一行 default 值, 重開終端機或進去別的專案 node 會預設使用該版本, 可以設定 default 值
$ nvm alias default 8.11.2

如果還有其他問題建議閱讀官方文彥

參考文章
官方 Github

[Hexo]Plugin load failed: hexo-deployer-s3

發表於 2020-04-21 分類於 Hexo

今天照常下 $ hexo g -d 準備 deploy, 終端機錯誤訊息如下:

1
2
3
4
5
6
ERROR Plugin load failed: hexo-deployer-s3
.(略)
.(略)
.(略)
.(略)
ERROR Deployer not found: s3

不知道為何 Plugin 一直 load 失敗
重新 yarn install, 整個 node_module 砍掉重安裝都一樣
查了一下都沒人遇到類似情況
後來降 node 版本竟然就解決了XD
這篇文章單純記錄一下狀況

$ node -v
v12.14.0 失敗
v8.11.2 正常

P.S. 我用 nvm

[Rails] 新增 rails 6 專案

發表於 2020-04-21 分類於 Rails

最近新增了一個專案玩玩, 直接使用 rails 6, 挺簡單沒有太多雷的, webpacker 變成 default 要安裝了

$ ruby -v ruby 如果 2.5.0 以下需要更新
$ rvm install 2.6.3 我是直接用 rvm 更新

$ gem install rails 會去抓 github 上最新版本的 rails
或者想指定版本
$ gem install rails -v '6.0.0'

啟動新專案
$ rails new repo_name
$ cd repo_name
$ bundle install
$ rails webpacker:install
$ rails start

應該就可以成功開啟 rails server 囉

參考文章
Rails 6 released - What does that mean for you?

[Ruby] include vs extend

發表於 2020-04-04 更新於 2020-04-21 分類於 Ruby

精簡版

include 給 instance object 用
extend 給 class object 用

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module ForClass
def speak
puts 'Hello'
end
end

module ForInstance
def run
puts 'Running'
end
end

class Person
extend ForClass
include ForInstance
end

Person.speak # Hello
Person.new.run # Running

Person.run # NoMethodError
Person.new.speak # NoMethodError

如果只想知道怎麼使用看到這邊就可以了, 如果想知道原理就繼續往下看

再來發生的情況其實如下

methods 找尋順序

Ruby 世界如果當前 class 沒有該 method, 則會一層一層往上找, 直到最上層的 class 還找不到就會回傳 NoMethodError, 我們先 print 出所有父類(superclass)

1
2
3
4
puts Person.new.class # Person
puts Person.superclass # Object
puts Object.superclass # BasicObject
puts BasicObject.superclass # nil

由此可知最上層是父類是 BasicObject, 在 call superclass 會因為關聯不到父類而回傳 NilClass 了

所以可以說 methods 找尋順序的找尋順序如下:
Person > Object > BasicObject

但在剛剛的例子中 include 或 extend 了兩個 module 都會插入找尋順序, 且越晚插入的將會排序越前面, 所以剛剛的例子找尋順序如下:
Person > ForInstance > ForClass > Object > BasicObject

class object methods vs instance object methods

在 class object 中, 兩種 methods 繼承上是分離的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A
def self.speak
puts 'class Hi'
end

def speak
puts 'instance Hi'
end
end

class B < A
end

B.speak # class Hi
B.new.speak # instance Hi

而在 module 則沒有分, 單純只對 method 名稱, 給予 self 反而找不到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module Action
def self.speak
puts 'class Hi'
end

def speak
puts 'instance Hi'
end
end

class Person
extend Action
include Action
end

Person.speak # instance Hi
Person.new.speak # instance Hi

其實只要把所有 self 都印出來就不難理解了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module Action
puts "Self_1 is #{self}"
def self.speak
puts "Self_2 is #{self}"
puts 'class Hi'
end

def speak
puts "Self_3 is #{self}"
puts 'instance Hi'
end
end

class Person
puts "Self_4 is #{self}"
extend Action
end

Person.speak
# 執行結果如下:
# Self_1 is Action
# Self_4 is Person
# Self_3 is Person
# instance Hi

  1. 可以看到程式碼由上往下跑的時候會先初始化 module Action 跟 class Person

    此時先 print 出 Self_1 is Action 再來 Self_4 is Person

  2. 再來執行 Person.speak, 因為 Person 沒有 speak 這個 method 所以照著 methods 找尋順序找到了 module Action 的 speak

    此時 print 出 Self_3 is Person

  3. 再來執行最後的 puts

    print 出 instance Hi

最後疑惑那我要怎麼呼叫 module Action 的 self.speak 呢?
其實很簡單就把 self 改為 Action 這個 object 就好

1
2
3
Action.speak
# Self_2 is Action
# class Hi

關於 self 以及 ruby 物件導向的觀念可以參考我另外寫的文章[Ruby] 物件導向

123…18

Kurt Hsu

Progress One Percent Every Day
171 文章
55 分類
163 標籤
RSS
© 2020 Kurt Hsu
由 Hexo 強力驅動 v3.8.0
|
主題 – NexT.Muse v7.3.0