class 中私有方法的 NoMethodError(未定义方法)

NoMethodError (undefined method) from Private method in class

为什么我不能在 class 中使用私有方法?如何修复我的代码以防止错误?

module CarRegistration
  class Basics < Base

    fields_of_model(:car).each do |attr|
      delegate attr.to_sym, "#{attr}=".to_sym, to: :car
    end

    private

    car_structure = #array of hashes

    def fields_of_model(model)
      car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
    end
end

错误

NoMethodError (undefined method `fields_of_model' for CarRegistration::Basics:Class):

因为你在def-end子句中写了方法not;你应该这样写

def my_method
  fields_of_model(:car).each do |attr|
    delegate attr.to_sym, "#{attr}=".to_sym, to: :car
  end
end

这就是错误消息显示 CarRegistration::Basics:Class 而不是 CarRegistration::Basics

的原因

这是一个有效的示例代码。 通常不需要在Module里面放一个class,但是如果你因为某些原因必须要放,这是一个办法。

module CarRegistration
  class Basics < Object
    def run(model)
      fields_of_model(model)
    end

    private

    def fields_of_model(model)
      puts model
    end
  end
end

a = CarRegistration::Basics.new
a.run('xyz')  # => 'xyz' is printed.

我认为您遇到了很多问题。

首先,您已将 fields_of_model 定义为实例方法,此处:

def fields_of_model(model)
  car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
end

但您正试图从 class 调用它,此处:

fields_of_model(:car).each do |attr|
  delegate attr.to_sym, "#{attr}=".to_sym, to: :car
end

因此,您需要将 fields_of_model 设为 class 方法,并在调用前定义它。类似于:

module CarRegistration
  class Basics < Base

    private

    car_structure = #array of hashes

    class << self

      def fields_of_model(model)
        car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
      end

    end

    fields_of_model(:car).each do |attr|
      delegate attr.to_sym, "#{attr}=".to_sym, to: :car
    end

end

我认为 car_structure 变量也会有问题,因为它超出了 class 方法的范围。所以,我觉得你需要做一个class级的实例变量。所以,试试这个:

module CarRegistration
  class Basics < Base

    @car_structure = #array of hashes

    class << self

      def fields_of_model(model)
        @car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
      end

      private :fields_of_model

    end

    fields_of_model(:car).each do |attr|
      delegate attr.to_sym, "#{attr}=".to_sym, to: :car
    end

end

请注意,我使用 private :fields_of_model 将 class 方法 :fields_of_models 设为私有。

为了演示整个事情,我做了这个 RSpec 测试:

require 'rails_helper'

class Car

  attr_accessor *%w(
    color
    make
    year
  ).freeze

end

module CarRegistration
  class Basic

    @car_structure = [
      {model: :car, name: :color},
      {model: :car, name: :make},
      {model: :car, name: :year}
    ]

    class << self

      def fields_of_model(model)
        @car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
      end

      private :fields_of_model

    end

      fields_of_model(:car).each do |attr|
        delegate attr.to_sym, "#{attr}=".to_sym, to: :car
      end

      def car
        @car ||= Car.new 
      end

  end
end

RSpec.describe CarRegistration::Basic do
    it "has :fields_of_model as a private class method" do 
      expect(CarRegistration::Basic.public_methods).not_to include(:fields_of_model)
      expect(CarRegistration::Basic.private_methods).to include(:fields_of_model)
    end
    it "responds to :color and :color=" do
      expect(car_registration).to respond_to(:color)
      expect(car_registration).to respond_to(:color=)
    end
    it "sets and gets attributes on car" do
      expect(car_registration.color).to be_nil
      expect(car_registration.car.color).to be_nil
      car_registration.color = :red
      expect(car_registration.car.color).to eq(:red)
      expect(car_registration.color).to eq(:red)
      expect(car_registration.instance_variable_get(:@color)).to be_nil 
    end
end

def car_registration
  @car_registration ||= described_class.new
end

其中,当 运行 时,产生:

CarRegistration::Basic
  has :fields_of_model as a private class method
  responds to :color and :color=
  sets and gets attributes on car

Finished in 0.733 seconds (files took 27.84 seconds to load)
3 examples, 0 failures

顺便说一句,将此代码放在 def-end 之外的 class 中很好,而不是问题的根源。其实很正常。

此外,我会注意到 Jörg W Mittag 想说:

I am one of those Ruby Purists who likes to point out that there is no such thing as a class method in Ruby. I am perfectly fine, though, with using the term class method colloquially, as long as it is fully understood by all parties that it is a colloquial usage. In other words, if you know that there is no such thing as a class method and that the term "class method" is just short for "instance method of the singleton class of an object that is an instance of Class", then there is no problem. But otherwise, I have only seen it obstruct understanding.

让各方都充分理解,class方法这个术语是在其口语中使用的。