Class_eval 不在每个块内工作

发布于 2024-12-26 17:53:19 字数 1180 浏览 1 评论 0原文

我定义了一个模块来扩展 ActiveRecord。

就我而言,我必须使用作为 compound_datetime 类方法的参数给出的符号来生成实例方法。当在 each 块外部而不是在其内部调用 class_eval 时,它会起作用;在后一种情况下,我收到未定义的方法错误。

有谁知道我做错了什么?

module DateTimeComposer
  mattr_accessor :attrs
  @@attrs = []

  module ActiveRecordExtensions
    module ClassMethods
      def compound_datetime(*attrs)
        DateTimeComposer::attrs = attrs
        include ActiveRecordExtensions::InstanceMethods
      end
    end

    module InstanceMethods
      def datetime_compounds
        DateTimeComposer::attrs
      end

      def self.define_compounds(attrs)
        attrs.each do |attr|
          class_eval <<-METHODS
            def #{attr.to_s}_to()
              puts 'tes'
            end
          METHODS
        end
      end

      define_compounds(DateTimeComposer::attrs)
    end
  end
end


class Account < ActiveRecord::Base
  compound_datetime :sales_at, :published_at
end

当我尝试访问该方法时:

Account.new.sales_at_to

我收到 MethodError: undefined method sales_at_to for #

I have defined a module to extend ActiveRecord.

In my case I have to generate instance methods with the symbols given as arguments to the compound_datetime class method. It works when class_eval is called outside the each block but not inside it; in the latter case I get an undefined method error.

Does anyone know what I am doing wrong?

module DateTimeComposer
  mattr_accessor :attrs
  @@attrs = []

  module ActiveRecordExtensions
    module ClassMethods
      def compound_datetime(*attrs)
        DateTimeComposer::attrs = attrs
        include ActiveRecordExtensions::InstanceMethods
      end
    end

    module InstanceMethods
      def datetime_compounds
        DateTimeComposer::attrs
      end

      def self.define_compounds(attrs)
        attrs.each do |attr|
          class_eval <<-METHODS
            def #{attr.to_s}_to()
              puts 'tes'
            end
          METHODS
        end
      end

      define_compounds(DateTimeComposer::attrs)
    end
  end
end


class Account < ActiveRecord::Base
  compound_datetime :sales_at, :published_at
end

When I try to access the method:

Account.new.sales_at_to

I get a MethodError: undefined method sales_at_to for #<Account:0x007fd7910235a8>.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

宁愿没拥抱 2025-01-02 17:53:19

您在 InstanceMethods 模块定义末尾调用 define_compounds(DateTimeComposer::attrs)。此时,在代码中,attrs 仍然是一个空数组,selfInstanceMethods 模块。

这意味着不会定义任何方法,即使定义了方法,它们也会绑定到 InstanceMethods 的元类,使它们成为该模块的类方法,而不是该模块的实例方法您的 Account 类。

发生这种情况是因为 InstanceMethods 模块定义内的方法调用按照 Ruby 解释器所看到的方式进行计算,而当您调用 include ActiveRecordExtensions::InstanceMethods不会 >。这意味着可以在最不寻常的地方运行任意代码,例如在类定义中

要解决该问题,您可以使用 included 回调< /a> 由 ruby​​ 提供,每当一个模块包含在另一个模块中时就会调用它:

module InstanceMethods
  # mod is the Class or Module that included this module.
  def included(mod)
    DateTimeComposer::attrs.each do |attr|
      mod.instance_eval <<-METHODS
        def #{attr.to_s}_to
          puts 'tes'
        end
      METHODS
    end
  end
end

作为附加建议,您应该能够通过在调用 compound_datetime 时简单地定义方法来实现相同的结果,因此消除对attrs 全局类变量。

但是,如果您必须访问声明为复合日期时间的字段,则应使用类实例变量,这些变量对于每个类都是唯一的,并且不在层次结构中共享:

module ClassMethods
  def compound_datetime(*attrs)
    @datetime_compounds = attrs
    attrs.each do |attr|
      instance_eval <<-METHODS
        def #{attr.to_s}_to
          puts 'tes'
        end
      METHODS
    end
  end

  def datetime_compounds; @datetime_compounds; end;
end

class Account < ActiveRecord::Base
  compound_datetime :sales_at, :published_at
end

class AnotherModel < ActiveRecord::Base
  compound_datetime :attr1, :attr2
end

Account.datetime_compounds
 => [ :sales_at, :published_at ]

AnotherModel.datetime_compounds
 => [ :attr1, :attr2 ]

You are calling define_compounds(DateTimeComposer::attrs) at the end of the InstanceMethods module definition. At that point in the code, attrs is still an empty array, and self is the InstanceMethods module.

This means no methods will be defined, and even if they were, they would be bound to InstanceMethods's metaclass, making them class methods of that module, not instance methods of your Account class.

This happens because method calls inside the InstanceMethods module definition are evaluated as they are seen by the ruby interpreter, not when you call include ActiveRecordExtensions::InstanceMethods. An implication of this is that it is possible to run arbitrary code in the most unusual of places, such as within a class definition.

To solve the problem, you could use the included callback provided by ruby, which is called whenever a module is included in another:

module InstanceMethods
  # mod is the Class or Module that included this module.
  def included(mod)
    DateTimeComposer::attrs.each do |attr|
      mod.instance_eval <<-METHODS
        def #{attr.to_s}_to
          puts 'tes'
        end
      METHODS
    end
  end
end

As an additional suggestion, you should be able to achieve the same result by simply defining the methods when compound_datetime is called, thus eliminating the dependence on the attrs global class variable.

However, if you must have access to the fields which were declared as compound datetime, you should use class instance variables, which are unique to each class and not shared on the hierarchy:

module ClassMethods
  def compound_datetime(*attrs)
    @datetime_compounds = attrs
    attrs.each do |attr|
      instance_eval <<-METHODS
        def #{attr.to_s}_to
          puts 'tes'
        end
      METHODS
    end
  end

  def datetime_compounds; @datetime_compounds; end;
end

class Account < ActiveRecord::Base
  compound_datetime :sales_at, :published_at
end

class AnotherModel < ActiveRecord::Base
  compound_datetime :attr1, :attr2
end

Account.datetime_compounds
 => [ :sales_at, :published_at ]

AnotherModel.datetime_compounds
 => [ :attr1, :attr2 ]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文