来自类中Private方法的NoMethodError(未定义方法)

时间:2019-03-08 02:42:56

标签: ruby-on-rails ruby ruby-on-rails-5.2

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

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(未定义的方法“ fields_of_model”,用于   CarRegistration :: Basics:Class):

2 个答案:

答案 0 :(得分:3)

我认为您在这里遇到了许多问题。

首先,您已经将fields_of_model定义为实例方法,在这里:

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

但是您正试图从班级中调用它,

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

因此,您需要将fields_of_model设为一个类方法,并在调用它之前对其进行定义。像这样:

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变量的问题,因为它将超出类方法的范围。因此,我认为您需要制作一个类级实例变量。因此,尝试一下:

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

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

为演示整个过程,我进行了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之外的类中很好,而不是问题的根源。实际上,这很正常。

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

  

我是那些想要指出Ruby中没有类方法之类的Ruby Purists的人之一。不过,我很好地使用了 class方法这个术语,只要各方都完全理解这是一个俗语用法。换句话说,如果您知道,就没有类方法之类的东西,并且术语“类方法”只是“作为实例的对象的单例类的实例方法”的简称的Class”,那么就没有问题。但是否则,我只会看到它妨碍理解。

让所有各方都完全理解,术语“ <类>类方法” 在上面用的是口语含义。

答案 1 :(得分:-1)

因为您在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内放一个类,但是如果您由于某种原因必须这样做,这是一种方法。

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.