2017-03-22 83 views
2
class Cat 
    SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')] 

    attr_accessor :name 

    def initialize(name) 
    @name = name 
    end 
end 

使用當前類我得到一個錯誤在常量聲明

ArgumentError: wrong number of arguments(1 for 0)

因爲initialize不會被再次定義。

如果我把定義的結尾:

class Cat  
    attr_accessor :name 

    def initialize(name) 
    @name = name 
    end 

    SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')] 
end 

它的工作原理。

是否可以在文件頂部保持常量聲明?

回答

1

我認爲,你可以使用這一招:

class Cat 
    def self.const_missing(name) 
    [Cat.new('John'), Cat.new('Alfred')] if name == :SUPERSTARS 
    end 

    attr_accessor :name 

    def initialize(name) 
    @name = name 
    end 
end 
+1

一個_can_做到這一點,但不應該。 :) –

+2

@SergioTulentsev這是另一個問題;) –

+0

是的,它絕對是:) –

0

的問題是,類定義只是達到定義時被執行的代碼。這就像問你是否可以訪問你尚未定義的局部變量的值。

可能的解決方法是延遲對常量的評估,直到執行類體。請注意,這不是很值得,它表明,不應該在實踐中使用一個有效的解決方案:

# BasicObject for minimal amount of methods 
class Retarded < BasicObject 
    def initialize(&value) 
    @value = value 
    end 

    # Make sure we remove as many methods as we can to 
    # make the proxy more transparent. 
    (instance_methods - %i(__id__ __send__ __binding__)).each do |name| 
    undef_method name 
    end 

    def method_missing(*args, &block) 
    # Get the actual value 
    value = @value.call 
    # Suppress warnings while we traverse constants 
    warn_level, $VERBOSE = $VERBOSE, nil 

    traversed_modules = [] 
    constant_finder = -> (root) do 
     constant_name = root.constants.find do |constant| 
     # We undefed ==, so we need a different way to compare. 
     # Given that this class should be used for constant values in place, 
     # comparing object ids does the job. 
     root.const_get(constant).__id__ == __id__ 
     end 

     if constant_name 
     # Just set the constant to the actual value 
     root.const_set(constant_name, value) 
     else 
     # Recursively search for the containing module of the constant 
     root.constants.each do |child_name| 
      child_constant = root.const_get(child_name) 
      if child_constant.is_a?(::Module) && 
       !traversed_modules.include?(child_constant) 
      # Handle circular references 
      traversed_modules.push child_constant 
      constant_finder.(child_constant) 
      end 
     end 
     end 
    end 

    # ::Object is the root module where constants are contained 
    constant_finder.(::Object) 

    # Bring back warnings 
    $VERBOSE = warn_level 

    # We have already found the constant and set its value to whatever was 
    # passed in the constructor. However, this method_missing was called 
    # in an attempt to call a method on the value. We now should make that 
    # invocation. 
    value.public_send(*args, &block) 
    end 
end 

# I know, the example is mononic. 
class EdwardKing 
    DEAD = Retarded.new { [new('I'), new('II'), new('III')] } 

    def initialize(number) 
    @number = number 
    end 

    def whoami 
    "Edward #{@number} of England" 
    end 
end 

p EdwardKing::DEAD 
    # [#<EdwardKing:0x007f90fc26fd10 @number="I">, #<EdwardKing:0x007f90fc26fc70 @number="II">, #<EdwardKing:0x007f90fc26fc20 @number="III"> 
p EdwardKing::DEAD.map(&:whoami) 
    # ["Edward I of England", "Edward II of England", "Edward III of England"] 
0

Avdi格林在他的空閒rubytapas的插曲叫"Barewords"建議您不要直接使用常量。相反,將它們包裝在一個方法中。而當你有一種方法時,你甚至不需要常量(這在Ruby中並不是常數)。

所以,你可以做這樣的事情:

class Cat 
    def self.superstars 
    @superstars ||= [Cat.new('John'), Cat.new('Alfred')] 
    end 

    attr_accessor :name 

    def initialize(name) 
    @name = name 
    end 
end 

唯一的區別是,你現在把它叫做Cat.superstars,而不是Cat::SUPERSTARS。你的代碼現在可以工作,看起來也更好!我稱之爲雙贏。 :)

+0

也可以使用不太受歡迎的調用者'::',它會像:'Cat :: superstars' :)) –

+0

@AlexGolubenko:是的,我不會使用這種語法同樣的原因:代碼的一致性。 –

+0

當然,這只是一個笑話) –