Pergunta

Pergunta: Usando o Ruby é simples adicionar personalizado métodos para as classes existentes, mas como fazer para você adicionar personalizado propriedades?Aqui está um exemplo do que estou tentando fazer:

myarray = Array.new();
myarray.concat([1,2,3]);
myarray._meta_ = Hash.new();      # obviously, this wont work
myarray._meta_['createdby'] = 'dreftymac';
myarray._meta_['lastupdate'] = '1993-12-12';

## desired result
puts myarray._meta_['createdby']; #=> 'dreftymac'
puts myarray.inspect()            #=> [1,2,3]

O objetivo é construir a definição de classe de tal maneira que as coisas que não funcionam no exemplo acima irá funcionar como esperado.

Atualização: (esclarecer a pergunta), Um aspecto que foi deixado de fora da pergunta:também é um objetivo para adicionar "valores padrão" que normalmente seria no inicializar método de classe.

Atualização: (por que fazer) Normalmente, é muito simples basta criar uma classe que herda de Matriz (ou qualquer que seja incorporado na classe que você deseja emular).Esta questão deriva de alguns "testes" só de código e não é uma tentativa de ignorar isso geralmente aceitável abordagem.

Foi útil?

Solução

Lembre-se que em Ruby, você não tem acesso aos atributos (variáveis de instância) fora da instância.Você só tem acesso a uma instância pública de métodos.

Você pode usar attr_accessor para criar um método de uma classe que age como uma propriedade, como você descreve:

irb(main):001:0> class Array
irb(main):002:1>  attr_accessor :_meta_
irb(main):003:1> end
=> nil
irb(main):004:0> 
irb(main):005:0* x = [1,2,3]
=> [1, 2, 3]
irb(main):006:0> x._meta_ = Hash.new
=> {}
irb(main):007:0> x._meta_[:key] = 'value'
=> "value"
irb(main):008:0> 

Uma forma simples de fazer uma inicialização padrão para um acessor, nós vamos precisar, basicamente, reimplementar attr_accessor nós mesmos:

class Class
  def attr_accessor_with_default accessor, default_value
    define_method(accessor) do
      name = "@#{accessor}"
      instance_variable_set(name, default_value) unless instance_variable_defined?(name)
      instance_variable_get(name)
    end

    define_method("#{accessor}=") do |val|
      instance_variable_set("@#{accessor}", val)
    end
  end
end

class Array
    attr_accessor_with_default :_meta_, {}
end

x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_

y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_

Mas espera!A saída é incorreto:

{:key=>"value"}
{:foo=>"bar", :key=>"value"}

Criamos um fechamento em torno do valor padrão de um literal de hash.

Uma maneira melhor pode ser simplesmente utilizar um bloco:

class Class
  def attr_accessor_with_default accessor, &default_value_block
    define_method(accessor) do
      name = "@#{accessor}"
      instance_variable_set(name, default_value_block.call) unless instance_variable_defined?(name)
      instance_variable_get(name)
    end

    define_method("#{accessor}=") do |val|
      instance_variable_set("@#{accessor}", val)
    end
  end
end

class Array
    attr_accessor_with_default :_meta_ do Hash.new end
end

x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_

y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_

Agora, a saída está correta porque Hash.new é chamado toda vez que o valor padrão é obtido, em oposição a reutilização do mesmo literal de hash de cada vez.

{:key=>"value"}
{:foo=>"bar"}

Outras dicas

Uma propriedade não é apenas um getter e um setter? Nesse caso, você não poderia fazer:

class Array
  # Define the setter
  def _meta_=(value)
    @_meta_ = value
  end

  # Define the getter
  def _meta_
    @_meta_
  end
end

Então, você pode fazer:

x = Array.new
x._meta_
# => nil

x._meta_ = {:name => 'Bob'}

x._meta_
# => {:name => 'Bob'}

Isso ajuda?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top