Ruby: appel de la méthode de classe à partir d'une instance

347

Dans Ruby, comment appelez-vous une méthode de classe à partir d'une des instances de cette classe? Dis que j'ai

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

la ligne Truck.default_makerécupère la valeur par défaut. Mais existe-t-il un moyen de le dire sans le mentionner Truck? Il semble qu'il devrait y en avoir.

Peter
la source

Réponses:

563

Plutôt que de faire référence au nom littéral de la classe, vous pouvez simplement appeler à l'intérieur d'une méthode d'instance 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

Les sorties:

Méthode de classe: Foo
Méthode d'instance: Foo
Mark Rushakoff
la source
7
je voudrais voir un raccourci en ruby ​​pour appeler une méthode de classe à partir d'une instance. ie:> some_class_method au lieu de self.class.some_class_method
phoet
7
bien que ce soit la bonne réponse, il est dommage que "self.class" soit plus typant et moins facile à lire que le nom de classe "Truck". eh bien ...
Matt Connolly
22
@MattConnolly, c'est relatif, si le nom de votre classe est SalesforceSyncJobalors c'est plus court;)
drewish
29
@MattConnolly, également en utilisant self.classélimine le besoin de recherche / remplacement si vous renommez la classe.
Gus Shortz
8
@GusShortz true. En outre, self.class fonctionne mieux s'il existe une sous-classe.
Matt Connolly
183

L'utilisation self.class.blahn'est PAS la même chose que l'utilisation ClassName.blahen matière d'héritage.

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
la source
58
Cela semble être une réponse à la réponse acceptée plutôt qu'une réponse à la question.
zhon
16
@zohn - vrai, mais c'est toujours un contexte utile lorsque l'on considère quoi utiliser.
Matt Sanders
1
@MattSanders utilise simplement un commentaire dans ces cas.
nandilugio
1
@hlcs self.classest correct pour préserver l'héritage. même s'il make1()est défini dans Truck, il fait référence à BigTruckla méthode de classe.
Kaiser Shahid
Class_name.method_name fonctionne parfaitement
Houda M
14

Pour accéder à une méthode de classe à l'intérieur d'une méthode d'instance, procédez comme suit:

self.class.default_make

Voici une solution alternative à votre problème:

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

Utilisons maintenant notre classe:

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

t = Truck.new
t.make
# => "Toyota"
t.year
# => nil
Harish Shetty
la source
make ne doit pas être une méthode d'instance. c'est plus une sorte d'usine, qui devrait être liée à la classe plutôt qu'une instance
phoet
6
@phoet Le mot marque désigne la marque d'une voiture (comme dans Toyota, BMW, etc.) englishforums.com/English/AMakeOfCar/crcjb/post.htm . La nomenclature est basée sur les besoins de l'utilisateur
Harish Shetty
8

Si vous avez accès à la méthode déléguée, vous pouvez le faire:

[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"

Alternativement, et probablement plus propre si vous avez plus d'une ou deux méthodes que vous souhaitez déléguer à la classe et à l'instance:

[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"

Un mot d'avertissement:

Ne vous contentez pas au hasard de delegatetout ce qui ne change pas d'état en classe et en instance, car vous allez rencontrer des problèmes de conflit de noms étranges. Faites-le avec parcimonie et seulement après avoir vérifié que rien d'autre n'est écrasé.

bbozo
la source
6
self.class.default_make
yfeldblum
la source
5

Vous le faites de la bonne façon. Les méthodes de classe (similaires aux méthodes «statiques» en C ++ ou Java) ne font pas partie de l'instance, elles doivent donc être référencées directement.

Sur cette note, dans votre exemple, vous feriez mieux de faire de 'default_make' une méthode régulière:

#!/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()

Les méthodes de classe sont plus utiles pour les fonctions de type utilitaire qui utilisent la classe. Par exemple:

#!/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
la source
2
Je ne suis pas d'accord que cela default_makedevrait être une méthode d'instance. Même si c'est plus simple pour ces exemples, ce n'est pas la bonne sémantique - la valeur par défaut est un produit de la classe, pas des objets qui appartiennent à la classe.
Peter
1
@Peter voudriez-vous expliquer cela en termes plus simples? J'apprends juste que les réponses de Ruby et Maha me semblent parfaites.
Marlen TB
1
@ MarlenT.B. en regardant en arrière, je ne suis pas sûr qu'il y ait trop à apprendre ici - je ne faisais que discuter du meilleur endroit où mettre la méthode, et je n'achète plus mon propre argument aussi fortement! :)
Peter
2
Je suis également en désaccord. Que quelque chose soit une méthode de classe n'a rien à voir avec "l'utilité". Il s'agit de savoir si la méthode s'applique conceptuellement à la classe ou à un objet de cette classe. Par exemple, chaque camion a un numéro de série différent, donc serial_number est une méthode d'instance (avec la variable d'instance correspondante). Sur l'autre type de véhicule (qui renvoie "camion") devrait être une méthode de classe car c'est une propriété de tous les camions, pas un camion en particulier
vish
3

Un de plus:

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
la source
1

Voici une approche sur la façon dont vous pourriez implémenter une _classméthode qui fonctionne comme self.classdans cette situation. Remarque: Ne l'utilisez pas dans le code de production, c'est pour l'intérêt :)

De: Pouvez-vous évaluer le code dans le contexte d'un appelant dans Ruby? et aussi 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

Peut-être que la bonne réponse est de soumettre un patch pour Ruby :)

capitainepete
la source
-6

Similaire à votre question, vous pouvez utiliser:

class Truck
  def default_make
    # Do something
  end

  def initialize
    super
    self.default_make
  end
end
Daniel
la source