關於class裡呼叫class Method觀念想要釐清


#1

這幾天在釐清 class method 的觀念
看到網路上的文章
文章
裡面有一段

module ActiveRecord
           class Base
                 
              def self.validates_presence_of(...)
                   # make sure present
              end
          end
end

class Foo < ActiveRecord::Base
     validates_presence_of :bar
end

在我的認知
Foo繼承ActiveRecord::Base
可以使用Base的方法
想了解他的觀念
為什麼沒有使用self就可使用validates_presence_of 的類別方法


我也自己try了一次程式碼

class Example3 < Sample
    valid :item2
    attr_accessor :item2

    def initialize
		@item2 = 333
    end
end

example3 = Example3.new
puts example3.item2

#item2
#333

valid :item2 的 :item2 應該是傳入 getter 的方法吧?
我該怎麼使用才能使她傳入跟new一樣的值呢

困惑了好幾天
麻煩囉感謝


#2

首先,Ruby 的 class 裡面可以定義的方法 (method) 有兩種,一種是 instance method,一種是 singleton method

前者是一般我們常見的方法,我們常用來對實體 (instance) 呼叫,例如:

class Foo
  def bar
    1
  end
end

foo = Foo.new # => Foo
foo.bar # => 1

像這樣可以對 Foo 的實體 (Foo.new),進行操作的方法,就是 instance method;

另外一種,常見於 Module 或對單一 Class 進行操作的方法,如下:

class Foo2
  def self.bar2
    2
  end
end

Foo2.bar2 # => 2

這種方法只能對 Module 或 Class 本體直接操作,而不能透過其實體進行操作;

混合起來的範例就是:

class FooBar
  def foo
    1
  end

  def self.bar
    2
  end
end

FooBar.foo # => undefined method: 'foo'
FooBar.new.foo # => 1
FooBar.bar # => 2
FooBar.new.bar # => undefined method: 'bar'

現在我們瞭解了 instance method 跟 singleton method 的差別了,我們回來看你問的問題:

為什麼沒有使用 self 就可使用 validates_presence_of 的類別方法

從剛剛的範例知道, validates_presence_of 屬於 signleton method,是對整個 Class 本體進行操作,
針對你提出的範例,如下:

class Foo < ActiveRecord::Base
     validates_presence_of :bar
end

因為 validates_presence_of 是在 Foo 裡面直接對整個 class 操作,不是針對他的實體,所以是可以直接呼叫到 validates_presence_of 的。

而 Ruby 在對於一個不知道是變數還是方法的名稱,他的搜尋路徑是先檢查是不是區域變數?如果不是,那試試看是不是自己(self)的方法好了,所以也可以回答你為什麼不需要加 self. 的問題。

再來是第二個問題,先把你的範例整理一下,寫成等義的句子:

class Example3 < Sample
    valid(:item2)
    attr_accessor(:item2)

    def initialize
        @item2 = 333
    end
end

發現了嗎?
其實 vaildattr_accessor 都是 singleton method,但是 initialize 卻是 instance method,他們本來就活在不同的世界裡面,如果真的想要在 initialize 操作 singleton method 的話,可以使用 self.class.,例如:

class Foo
  def self.foo
    1
  end

  def bar
    self.class.foo
  end
end

Foo.new.foo # => 1

不過你這個問題:

valid :item2 的 :item2 應該是傳入 getter 的方法吧?
我該怎麼使用才能使她傳入跟new一樣的值呢

我只能回答你前半部,:item2 的確是被傳入 valid 了,可是他不是一個 getter;怎麼使用才能傳入跟 new 的值就不知道是什麼意思了,可能要請你回去想一下你的問題到底是什麼?XD


#3

幫你搜尋了一下站上相關問題的 post:


#4
valid :item2 的 :item2 應該是傳入 getter 的方法吧?
我該怎麼使用才能使她傳入跟new一樣的值呢?

抱歉這個問題沒有敘述的很好
我的意思是
當加入attr_accessor :item2 時,會產生getter 和 setter的方法,也會產生 :item2 跟 :item= 對吧?
那這時候使用的 valid :item2 應該是 attr_accessor 產生的 :item2 也就是 getter的方法產生的
我的理解是正確的嗎?
就像before_action :index
這個:index也是從index方法來的吧?

那這樣:item2裡面是什麼呢?
是方法嗎
如果是該怎麼傳值呢

我的Demo Code:

class Sample
       def self.valid(item)
          puts item
       end
end

class Example3 < Sample
   valid :item2
   attr_accessor :item2
   
   def initialize
	@item2 = 333
   end
end

example3 = Example3.new
puts example3.item2

#item2
#333

#5

我必須先釐清一個觀念 … Ruby 下其實沒有 magic,單純你有沒有看透一些基本的東西罷了 … & 可以的話請從基本開始,youtube 的基本教學中, day1 Ruby 那邊有詳細的 method 定義,而你的問題都是 … 你把 method 和看起來很 magic 的東西混在一起了 …

所謂的

attr_accessor :item2

其實是非常非常一般的 method 罷了,單純的寫在 class 內,所以你可以寫成

attr_accessor(:item2)

再來,這 method 是 Ruby 給你的,定義在 module 內,doc 在這邊,所有的物件預設都有繼承到 module

http://ruby-doc.org/core-2.0.0/Module.html

當然 Ruby kernel lib 都是 C 寫的就是,然而你可以完完全全做到一樣的東西,類似這篇的回文內的 code

and 我單純把 code 全寫在一起之類的

class C
  def self.attr_accessor_v2(method_name)
    inst_variable_name = "@#{method_name}".to_sym
    define_method method_name do
      instance_variable_get inst_variable_name
    end
    define_method "#{method_name}=" do |new_value|
      instance_variable_set inst_variable_name, new_value
    end
  end

  attr_accessor_v2 :hello_kitty
end

c = C.new
puts c.hello_kitty #=> nil
c.hello_kitty = 123
puts c.hello_kitty #=> 123

所以對我而言沒有 magic,很多人都以為啥鬼那麼 magic,但對我而言一點都沒有,一樣是 Java 那套老招一樣的 OOP,單純動態產生 method(hello_kitty() , hello_kitty=()),然後可以在初始階段順便執行而已( attr_accessor_v2 :hello_kitty 段 )

所以以上,缺少括號只是很一般的語法糖罷了,定義時期執行也只是方便性罷了,其餘都是 OOP 的根本哩,也沒有那種直接綁定的問題之類的,而 callback 只是弄個 array 做個 stack 來讓繼承的 class 在定義時期順便進行註冊,在適當的時間來做 callback 罷了(Rails 內部有更多的包裝,這邊用最簡 code 來做舉例),類似

class C
  # 定義域
  def self.reg_play(method_name)
    @@play ||= []
    @@play << method_name
    @@play.uniq!
  end
  def self.callback_me(obj , where_call)
    @@play.each do |method_name|
      obj.send(method_name , where_call)
    end
  end

  # 註冊,此時 method  還沒出生,單純註冊而已
  reg_play :cool
  reg_play :cute

  # callback 執行點
  def save(data)
    self.class.callback_me(self , 'before')
    puts "im save #{data}"
    self.class.callback_me(self , 'after')
    return self
  end
  
  private

  # 實作     
  def cool(where_call)
    puts "im cool from #{where_call}"
  end
  def cute(where_call)
    puts "im cute from #{where_call}"
  end
end

C.new.save(123)
#=> im cool from before
#=> im cute from before
#=> im save 123
#=> im cool from after
#=> im cute from after

當然你可以包裝得更好,類似 before & after 分離,有刪除機制有的沒的,anyway 看懂這簡易事件註冊機,你就會懂整個 callback 的運作哩,不管如何請記得:沒有 magic


#6

:item2 單純只是 Ruby 裡面一種叫做 Symbol 的物件而已,類似字串是用 "item3" 表達,而 Symbol:item4 表達。

使用 attr_accessor 的時候,只是幫你定義叫做 item2item2= 的 instance method 而已,所謂 getter 跟 setter 就只是 foofoo= 兩個方法而已,你也可以自己定義:

class FooBar
  def foo
    @foo
  end

  def foo=(val)
    @foo = val
  end
end

fb = FooBar.new
fb.foo = 123
fb.foo # => 123

但是自己定義要寫兩個方法的定義好麻煩,所以 Ruby 成就程式設計師的美德 —— 懶惰,製作了一個 attr_accessor 的方法來幫你快速定義,是的, attr_accessor 也是方法,只不過是 singleton method,當你使用他的時候,他就操作當前的 class,幫他新增 foofoo= 兩個方法。

至於為什麼 FooBar#foo= 方法可以在使用的時候寫成 foobar.foo = 1 而不是 foobar.foo=(1) 呢?
一是 Ruby 的方法呼叫本來就不需要帶括號,所以可以寫成 foobar.foo= 1 是很自然的;
那為什麼等號前面可以空格呢?這就是 Ruby 再次成就「懶」之禪學的地方了,一般我們要定義 setter 很自然就會定義成如下的情形:

class Foo
  def set_foo(v)
    @foo = v
  end

  def foo
    @foo
  end
end

f = Foo.new
f.set_foo(1)
f.foo # => 1

可是 = 等號本身就隱含賦值的意義了,如果可以不要加上那個 set_ 就更完美了!
所以,Ruby 就說:「那我們用 = 來代替 setter 的標記好了!」
所以在 Ruby 裡面只要是後面帶有 = 的方法,我們都可以在給一個參數的時候,讓那個等號左右都可以空白,這樣看起來也整齊。


#7

回到你的問題:

當加入attr_accessor :item2 時,會產生getter 和 setter的方法,也會產生 :item2 跟 :item= 對吧?

不太對, getter 跟 setter 就是 Example3#item2Example3#item2= 兩個方法而已
(我們會用 ClassName#instance_method 來表達某個 class 的 instance method)

那這時候使用的 valid :item2 應該是 attr_accessor 產生的 :item2 也就是 getter的方法產生的

不對, :item2 只是單純的 Symbol 物件而已,他沒有什麼意思,就是一個表示 item2 的物件

就像before_action :index
這個:index也是從index方法來的吧?

不對,這個 :index 也只是值為 index 的 Symbol 而已。

可能你會問,為什麼有字串(String)了,為什麼還要有 Symbol?
其實在 Ruby 裡面有一個儲存槽,叫做 Symbol pool,裡面存放了所有用過的 Symbol,然後用一個特殊的方法進行編碼
當你使用 Symbol 進行比對的時候,他就會直接去比對編碼,來進行加速,而且 Symbol 本身是不可變的,
一旦我們想要改變,然後建立新的 Symbol 放進 Symbol pool 裡面,而不是改變本來的,
String 在建立、修改上面比較方便,但是在比對上使用的資源會比 Symbol 多(因為沒有編碼來幫忙嘛),
這就是為什麼很多時候我們會使用 Symbol 而不是 String 來當作標記。

補充一下好了,為什麼 before_action :index 怎麼可以從 Symbol 變成呼叫方法呢?

其實 Ruby 有一個特殊的方法叫做 Object#call(name, *args)
他可以對一個物件直接呼叫一個你指定名稱的方法,
所以以下是等義的:

class Bar
  def bar
    @bar
  end

  def bar=(v)
    @bar = v
  end

  def foobar(a, b)
    a > b
  end
end

x = Bar.new
x.bar = 1
x.bar # => 1
x.call(:bar) # => 1 # same as x.bar

x.foobar(1, 2) # => false
x.call(:foobar, 1, 2) # => false

x.call(:bar=, 100)
x.bar # => 100

所以 before_action 只是去記錄你給他的方法名稱,然後在適當的時機使用 Obejct#call 去呼叫對應的方法而已
不知道這樣你能不能理解? :)


#8

完全了解了
昨天看到JC哥的講解
有了大概的了解
打算回覆出我的理解
一直想不到該怎麼回比較好
今天看到David哥的講解
就知道我的理解是對的了

感謝兩位:)