Ruby: Chamando o método de classe da instância

347

No Ruby, como você chama um método de classe de uma das instâncias dessa classe? Diga que eu tenho

class Truck
  def self.default_make
    # Class method.
    "mac"
  end

  def initialize
    # Instance method.
    Truck.default_make  # gets the default via the class's method.
    # But: I wish to avoid mentioning Truck. Seems I'm repeating myself.
  end
end

a linha Truck.default_makerecupera o padrão. Mas existe uma maneira de dizer isso sem mencionar Truck? Parece que deveria haver.

Pedro
fonte

Respostas:

563

Em vez de se referir ao nome literal da classe, dentro de um método de instância você pode simplesmente chamar self.class.whatever.

class Foo
    def self.some_class_method
        puts self
    end

    def some_instance_method
        self.class.some_class_method
    end
end

print "Class method: "
Foo.some_class_method

print "Instance method: "
Foo.new.some_instance_method

Saídas:

Método da classe: Foo
Método da instância: Foo
Mark Rushakoff
fonte
7
eu gostaria de ver algum atalho em ruby ​​para chamar um método de classe de uma instância. ou seja:> some_class_method em vez de
self.class.some_class_method
7
Embora essa seja a resposta certa, é uma pena que "self.class" seja mais digitado e menos fácil de ler do que o nome da classe "Caminhão". ... bem ...
Matt Connolly
22
@MattConnolly, é relativa, se o seu nome da classe é é SalesforceSyncJob, então é mais curto;)
drewish
29
@MattConnolly, também o uso self.classelimina a necessidade de pesquisa / substituição se você renomear a classe.
Gus Shortz
8
@GusShortz true. Além disso, self.class funciona melhor se houver uma subclasse.
quer
183

Usar self.class.blahNÃO é o mesmo que usar ClassName.blahquando se trata de herança.

class Truck
  def self.default_make
    "mac"
  end

  def make1
    self.class.default_make
  end

  def make2
    Truck.default_make
  end
end


class BigTruck < Truck
  def self.default_make
    "bigmac"
  end
end

ruby-1.9.3-p0 :021 > b=BigTruck.new
 => #<BigTruck:0x0000000307f348> 
ruby-1.9.3-p0 :022 > b.make1
 => "bigmac" 
ruby-1.9.3-p0 :023 > b.make2
 => "mac" 
Mark B
fonte
58
Isso parece ser uma resposta à resposta aceita e não uma resposta à pergunta.
zhon
16
@zohn - verdade, mas esse contexto ainda é útil quando se considera o que usar.
Matt Sanders
11
O @MattSanders apenas usa um comentário nesses casos.
Nandilugio 3/08
11
@hlcs self.classestá correto para preservar a herança. mesmo que make1()definido em Truck, é o BigTruckmétodo de classe de referência .
Kaiser Shahid
Class_name.method_name funciona perfeitamente #
Houda M
14

Para acessar um método de classe dentro de um método de instância, faça o seguinte:

self.class.default_make

Aqui está uma solução alternativa para o seu problema:

class Truck

  attr_accessor :make, :year

  def self.default_make
    "Toyota"
  end

  def make
    @make || self.class.default_make
  end

  def initialize(make=nil, year=nil)
    self.year, self.make = year, make
  end
end

Agora vamos usar nossa classe:

t = Truck.new("Honda", 2000)
t.make
# => "Honda"
t.year
# => "2000"

t = Truck.new
t.make
# => "Toyota"
t.year
# => nil
Harish Shetty
fonte
make não deve ser um método de instância. é mais uma espécie de fábrica, que deve ser vinculado à classe em vez de uma instância
phoet
6
@phoet A palavra make denota a marca de um carro (como Toyota, BMW etc.) englishforums.com/Portuguese/AMakeOfCar/crcjb/post.htm . A nomenclatura é baseada nos requisitos do usuário
Harish Shetty
8

Se você tiver acesso ao método delegado, poderá fazer o seguinte:

[20] pry(main)> class Foo
[20] pry(main)*   def self.bar
[20] pry(main)*     "foo bar"
[20] pry(main)*   end  
[20] pry(main)*   delegate :bar, to: 'self.class'
[20] pry(main)* end  
=> [:bar]
[21] pry(main)> Foo.new.bar
=> "foo bar"
[22] pry(main)> Foo.bar
=> "foo bar"

Como alternativa, e provavelmente mais limpo, se você tiver mais de um método ou dois que deseja delegar para a classe e instância:

[1] pry(main)> class Foo
[1] pry(main)*   module AvailableToClassAndInstance
[1] pry(main)*     def bar
[1] pry(main)*       "foo bar"
[1] pry(main)*     end  
[1] pry(main)*   end  
[1] pry(main)*   include AvailableToClassAndInstance
[1] pry(main)*   extend AvailableToClassAndInstance
[1] pry(main)* end  
=> Foo
[2] pry(main)> Foo.new.bar
=> "foo bar"
[3] pry(main)> Foo.bar
=> "foo bar"

Uma palavra de cautela:

Não basta aleatoriamente delegatetudo o que não muda de estado para classe e instância, porque você começará a encontrar problemas estranhos de conflito de nome. Faça isso com moderação e somente depois de verificar nada mais é esmagado.

bbozo
fonte
6
self.class.default_make
yfeldblum
fonte
5

Você está fazendo isso da maneira certa. Os métodos de classe (semelhantes aos métodos 'estáticos' em C ++ ou Java) não fazem parte da instância, portanto, precisam ser referenciados diretamente.

Nessa nota, em seu exemplo, seria melhor fazer com que 'default_make' seja um método regular:

#!/usr/bin/ruby

class Truck
    def default_make
        # Class method.
        "mac"
    end

    def initialize
        # Instance method.
        puts default_make  # gets the default via the class's method.
    end
end

myTruck = Truck.new()

Os métodos de classe são mais úteis para funções do tipo utilitário que usam a classe. Por exemplo:

#!/usr/bin/ruby

class Truck
    attr_accessor :make

    def default_make
        # Class method.
        "mac"
    end

    def self.buildTrucks(make, count)
        truckArray = []

        (1..count).each do
            truckArray << Truck.new(make)
        end

        return truckArray
    end

    def initialize(make = nil)
        if( make == nil )
            @make = default_make()
        else
            @make = make
        end
    end
end

myTrucks = Truck.buildTrucks("Yotota", 4)

myTrucks.each do |truck|
    puts truck.make
end
Jason Machacek
fonte
2
Eu discordo que default_makedeveria ser um método de instância. Mesmo que seja mais simples para esses exemplos, não é a semântica correta - o padrão é um produto da classe, não objetos que pertencem à classe.
Peter
11
@ Peter, você gostaria de explicar isso em termos mais simples? Só estou aprendendo que as respostas de Ruby e Maha parecem perfeitas para mim.
Marlen TB
11
@ MarlenT.B. olhando para trás, não tenho certeza de que há muito a ser aprendido aqui - eu estava apenas discutindo sobre onde era o melhor lugar para colocar o método e não compro meu próprio argumento com tanta força! :)
Peter
2
Eu também discordo. Se algo é um método de classe não tem nada a ver com "utilitário". É sobre se o método se aplica conceitualmente à classe ou a um objeto dessa classe. Por exemplo, cada caminhão tem um número de série diferente, portanto, serial_number é um método de instância (com variável de instância correspondente). Por outro VEHICLE_TYPE (que retorna "caminhão") deve ser um método de classe, porque isso é uma propriedade de todos os caminhões, não um caminhão especial
vish
3

Mais um:

class Truck
  def self.default_make
    "mac"
  end

  attr_reader :make

  private define_method :default_make, &method(:default_make)

  def initialize(make = default_make)
    @make = make
  end
end

puts Truck.new.make # => mac
Alexey
fonte
1

Aqui está uma abordagem sobre como você pode implementar um _classmétodo que funciona comoself.class para esta situação. Nota: Não use isso no código de produção, isso é pelo interesse :)

De: Você pode avaliar o código no contexto de um chamador em Ruby? e também http://rubychallenger.blogspot.com.au/2011/07/caller-binding.html

# Rabid monkey-patch for Object
require 'continuation' if RUBY_VERSION >= '1.9.0'
class Object
  def __; eval 'self.class', caller_binding; end
  alias :_class :__
  def caller_binding
    cc = nil; count = 0
    set_trace_func lambda { |event, file, lineno, id, binding, klass|
      if count == 2
        set_trace_func nil
        cc.call binding
      elsif event == "return"
        count += 1
      end
    }
    return callcc { |cont| cc = cont }
  end
end

# Now we have awesome
def Tiger
  def roar
    # self.class.roar
    __.roar
    # or, even
    _class.roar
  end
  def self.roar
    # TODO: tigerness
  end
end

Talvez a resposta certa seja enviar um patch para Ruby :)

capitão
fonte
-6

Semelhante à sua pergunta, você pode usar:

class Truck
  def default_make
    # Do something
  end

  def initialize
    super
    self.default_make
  end
end
Daniel
fonte