# 常在 controller 裡看到 before_action，它是一個方法嗎? 跟一般用 def 定義的有何不同？

> 

Published: 2016-04-28
URL: https://kaochenlong.com/class-method

---

在 Rails 專案中，常會在 Controller 裡看到這樣的寫法：

```ruby
class ProductController &lt; ApplicationController
  before_action :find_product, only: [:show, :edit, :update, :destroy]

  # .. 中略

  private
  def find_product
    @product = Product.find_by(id: params[:id])
  end
end
```

這是指在執行特定 Action 之前，先去執行 `find_product` 方法。

&lt;!--more--&gt;

在 Model 裡也常看到類似的寫法：

```ruby
class Article &lt; ApplicationRecord
  before_save :encrypt_password
  validates :title, presence: true

  private
  def encrypt_password
    # ....
  end
end
```

這是說在資料儲存之前，先執行 `encrypt_password` 方法對密碼進行加密。

在一般的 Ruby 專案裡也常會看到這樣的寫法：

```ruby
class Cat
  attr_accessor :age, :name
end

kitty = Cat.new
kitty.name = &quot;nancy&quot;
kitty.age = 10

puts kitty.name   # =&gt; nancy
puts kitty.age    # =&gt; 10
```

所以，在 Controller 裡的 `before_action` 或是 Model 裡 `before_save` 及 `validates`，或是那個 `attr_accessor` 到底是什麼來歷? 感覺好像是什麼設定還是屬性，或是關鍵字之類的東西?

## 類別方法

其實，這些看起來像設定或是屬性的東西，它們不過就是類別方法罷了。

```ruby
class Cat
  def self.my_attr_accessor(*field_name)
    # ... 實作
  end

  my_attr_accessor :age, :name
end
```

跟寫一般的方法沒什麼不同，一樣是使用 `def` 這個關鍵字來定義，只是在定義類別方法的時候，需要在方法名字前面加個 `self` ，這樣就可以定義出類別方法(事實上是 Singleton Method)，然後就可以直接在類別裡面使用。

至於這些類別方法裡面的實作稍微有些複雜，有的需要知道怎麼操作 Block，有的還需要知道怎麼動態的定義方法(例如使用 `define_method`)，我們另外再開一篇來介紹。

## 關鍵字?

有些人以為 `private`、`protected` 這些語法是關鍵字，事實上這些都只是方法罷了（Ruby 什麼東西沒有，方法最多了）。

Ruby 裡的關鍵字不算太多，有興趣可翻 Ruby 的原始碼出來看看，Ruby 大概定義了 40 個左右的關鍵字，大概就是 `if`、`else`、`def`、`class` 之類的。[參考連結](https://github.com/ruby/ruby/blob/trunk/defs/keywords)

但即使是關鍵字也不表示全部不能用，你要拿來定義方法也是可以的，例如：

```ruby
def if
  puts &quot;this is a if&quot;
end
```

在定義的時候不會出錯，但使用的時候就沒辦法了：

```ruby
if()
```

會得到錯誤訊息如下：

```
-:51: syntax error, unexpected end-of-input
shell returned 1
```

因為 Ruby 在判讀語法的時候，期待 `if` 後面還有一些東西，所以 Ruby 認為你還沒寫完然後被判定成語法錯誤。但如果是包在類別裡的話：

```ruby
class Joke
  def if
    puts &quot;this is a if&quot;
  end
end

Joke.new.if
```

這樣就能正常執行了 :)


