【问题标题】:Layer Supertype in ActiveRecord (Rails)ActiveRecord (Rails) 中的图层超类型
【发布时间】:2009-02-28 15:45:07
【问题描述】:

我正在开发一个 ruby​​ on rails 应用程序,我希望能够在每次保存之前对每个 AR 对象执行一个方法。

我想我会像这样创建一个图层超类型:

MyObject << DomainObject << ActiveRecord::Base

并使用我的特殊方法(基本上从对象的字符串属性中去除所有标签,如“H1”)在 DomainObject 中放置一个回调(before_save)。

问题是 rails 正在请求 domain_object 表,而我显然没有。

我的第二次尝试是对活动记录进行monkeypatch,如下所示:

module ActiveRecord
  class Base
    def my_method .... end
  end
end

并将其放在 lib 文件夹下。

这不起作用,它告诉我 my_method 未定义。

有什么想法吗?

【问题讨论】:

    标签: ruby-on-rails activerecord


    【解决方案1】:

    尝试为您的域对象使用抽象类。

    class DomainObject < ActiveRecord::Base
      self.abstract_class = true
      # your stuff goes here
    end
    

    使用抽象类,您正在创建一个不能有对象(不能被实例化)并且没有关联表的模型。

    来自 Strictly Untyped 中的 Rails: Where to put the 'other' files

    Rails 启动时不会加载 lib 中的文件。 Rails 已经覆盖了Class.const_missingModule.const_missing 以根据类名动态加载文件。事实上,这正是 Rails 加载模型和控制器的方式。

    所以将文件放在 lib 文件夹中,它不会在 Rails 启动时运行,也不会猴子修补 ActiveRecord::Base。您可以将文件放在config/initializers,但我认为还有更好的选择。

    【讨论】:

    • 谢谢mike,这正是我的意思,有什么想法为什么lib文件夹下的'monkeypatching'不起作用?如果您可以用它来更新答案,我相信它会被接受,我会结束这个问题。再次感谢
    • 一般情况下,如果你对 ActiveRecord::Base 有添加,你将它放在 lib 目录中,并包含在 environment.rb 中。不过,我同意抽象类可以更好地解决您的问题。
    【解决方案2】:

    我在之前的工作中使用的另一种从模型中剥离 HTML 标签的方法是创建一个插件。我们剥离的不仅仅是 HTML 标签,这里是 HTML 剥离部分:

    初始化程序(vendor/plugins/stripper/init.rb):

    require 'active_record/stripper'
    ActiveRecord::Base.class_eval do
      include ActiveRecord::Stripper
    end
    

    剥离代码(vendor/plugins/stripper/lib/active_record/stripper.rb):

    module ActiveRecord
      module Stripper
        module ClassMethods
          def strip_html(*args)
            opts = args.extract_options!
            self.strip_html_fields = args
            before_validation :strip_html
          end
        end
    
        module InstanceMethods
          def strip_html
            self.class.strip_html_fields.each{ |field| strip_html_field(field) }
          end
        private
          def strip_html_field(field)
            clean_attribute(field, /<\/?[^>]*>/, "")
          end
          def clean_attribute(field, regex, replacement)
            self[field].gsub!(regex, replacement) rescue nil
          end
        end
    
        def self.included(receiver)
          receiver.class_inheritable_accessor :strip_html_fields
          receiver.extend         ClassMethods
          receiver.send :include, InstanceMethods
        end
      end
    end
    

    然后在您的 MyObject 类中,您可以通过调用有选择地从字段中删除 html:

    class MyObject < ActiveRecord::Base
      strip_html :first_attr, :second_attr, :etc
    end
    

    【讨论】:

      【解决方案3】:

      已经给出的 HTML 剥离插件代码将处理问题中提到的特定用途。通常,将相同的代码添加到多个类(包括一个模块)中很容易做到这一点,而无需从某个公共基础继承所有内容,或者向 ActiveRecord 本身添加任何方法。

      module MyBeforeSave
        def self.included(base)
          base.before_save :before_save_tasks
        end
      
        def before_save_tasks
          puts "in module before_save tasks"
        end
      end
      
      class MyModel < ActiveRecord::Base
        include MyBeforeSave
      end
      
      >> m = MyModel.new
      => #<MyModel id: nil>
      >> m.save
      in module before_save tasks
      => true
      

      【讨论】:

      • 模块方法有缺点。它只能由 AR:Base 的实例包含,每个类都必须包含它,如果您想添加更常见(不相关)的行为,则必须创建另一个模块。我不明白为什么继承是个问题
      • 在每个类中包含一个模块并不比改变每个类的继承位置更有效。许多人会认为为每种类型的不相关行为创建一个新模块是一件好事,即关注点分离。如果您向基础添加 3 种类型的功能,但希望新类只有 2 种,该怎么办?
      • 我宁愿在新类上使用额外的方法,而不是在我的域的每个类上包含 3 行。这是一个明确的继承案例,将模块扔给问题只会使其变得更加复杂。检查你包含的方法,你确定它不神秘吗?
      • 我不是故意在这里粗鲁,我很欣赏你的观点,我理解这是一个完全有效的观点,只是我不分享它。
      【解决方案4】:

      我会猴子补丁 ActiveRecord::Base 并将文件放在 config/initializers 中:

      class ActiveRecord::Base
      
        before_create :some_method
      
        def some_method
        end
      
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多