Rails - 抽象类定义和文件命名的最佳实践

时间:2014-06-25 07:45:03

标签: ruby-on-rails model abstract-class

我想定义3个类:

  • a MotherClass(摘要,无法推断)
  • 一个SubClassA(继承自MotherClass
  • 一个SubClassB(继承自MotherClass

在Rails中声明它的最佳解决方案是什么?

1。将所有内容放入app / models /

    app / models / mother_class.rb 中的
  • MotherClass < AR::Base app_models / sub_class_a.rb
  • 中的
  • SubClassA < MotherClass app / models / sub_class_b.rb
  • 中的
  • SubClassB < MotherClass

优点:实施起来并不复杂

不方便:模特文件夹中的一大堆混乱

2。为两个子类创建模块

    app / models / mother_class.rb 中的
  • MotherClass < AR::Base app / models / mother_module / sub_class_a.rb中的
  • MotherModule::SubClassA < MotherClass
  • app / models / mother_module / sub_class_b.rb中的
  • MotherModule::SubClassB < MotherClass

优势:与解决方案1相同

不方便:使用不同的名称命名MotherModuleMotherClass,但它们的意思几乎相同

第3。为3个类创建一个模块

    app / models / mother_module / base.rb 中的
  • MotherModule::Base < AR::Base app / models / mother_module / sub_class_a.rb中的
  • MotherModule::SubClassA < MotherModule::Base
  • app / models / mother_module / sub_class_b.rb中的
  • MotherModule::SubClassB < MotherModule::Base

优点:非常干净

不方便:需要Base中的某些功能覆盖(例如table_name)


所以我的问题是:Rails中的最佳实践是什么? - 如何命名我的课程? - 他们的目录是什么?

3 个答案:

答案 0 :(得分:15)

首先,我认为你必须已经意识到ruby没有真正的抽象类。但我们可以近似这种行为。在这样做的同时,听起来你更倾向于组织结构,我将尝试解决这个问题。

但是,我必须先说,我很惊讶你从组织角度来看这个问题是如此强烈。首先,我的想法是我是否真的想要实现单表继承,然后让它驱动组织问题。 通常这里的答案是单表继承你真正想要的。但是......让我们潜入!

使用单表继承

以下是使用单表继承来利用和组织模型的标准方法:

# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
  # An "abstract" method
  def method1
    raise NotImplementedError, "Subclasses must define `method1`."
  end

  def method2
    puts method1 # raises NotImplementedError if `method1` is not redefined by a subclass
  end
end

# app/models/sub_class_a.rb
class SubClassA < MotherClass
  def method1
    # do something
  end
end

# app/models/sub_class_b.rb
class SubClassB < MotherClass
  def method1
    # do something
  end
end

鉴于上述情况,我们会在调用MotherClass.new.method2时收到异常,但在调用SubClassA.new.method2SubClassB.new.method2时则不会。所以我们对#34;摘要&#34;要求。在组织上,你把它称为模型文件夹中的一个大混乱......如果你有很多这些子类或其他东西,我可以理解。但是,请记住,在单表继承中,即使是父类也是一个模型,并且/应该可以这样使用!所以,如果您真的想要更好地组织模型文件系统,那么您可以自由地这样做。例如,你可以这样做:

  • app/models/<some_organizational_name>/mother_class.rb
  • app/models/<some_organizational_name>/sub_class_a.rb
  • app/models/<some_organizational_name>/sub_class_b.rb

在此,我们保留所有其他内容(即每个模型的代码)相同。我们不会以任何方式命名这些模型,我们只是组织它们。为了完成这项工作,现在我们已经将它们放在 models 文件夹的子文件夹中而没有任何其他线索(即没有命名空间),这只是帮助Rails找到模型的问题。他们)。有关此信息,请参阅this other Stack Overflow post。但是,简而言之,您只需将以下内容添加到 config / application.rb 文件中:

config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**/}')]

使用Mixins

如果您认为单表继承不是您想要的(并且它们通常不是真的),那么mixins可以为您提供相同的准抽象功能。而且,您可以再次灵活地进行文件组织。 mixins的共同组织模式是:

# app/models/concerns/mother_module.rb
module MotherModule
  extend ActiveSupport::Concern

  # An "abstract" method
  def method1
    raise NotImplementedError, "Subclasses must define `method1`."
  end

  def method2
    puts method1 # raises NotImplementedError if `method1` is not redefined
  end
end

# app/models/sub_class_a.rb
class SubClassA
  include MotherModule

  def method1
    # do something
  end
end

# app/models/sub_class_b.rb
class SubClassB
  include MotherModule

  def method1
    # do something
  end
end

使用这种方法,我们在调用SubClassA.new.method2SubClassB.new.method2时仍然不会出现异常,因为我们已经在&#34;子类&#34;中覆盖了这些方法。由于我们无法直接调用MotherModule#method1,因此它肯定是一种抽象方法。

在上述组织中,我们将MotherModule隐藏在 models / concerns 文件夹中。这是目前Rails中mixins的常见位置。你没有提到你所使用的rails版本,所以如果你还没有models/concerns文件夹,你想要制作一个,然后从中制作rails自动加载模型那里。这将再次在 config / application.rb 中完成,其中包含以下行:

config.autoload_paths += Dir[Rails.root.join('app', 'concerns', '{**/}')]

在我看来,使用mixins方法的组织简单明了,SubclassASubClassB是(显然)模型,因为它们包含MotherModule关注点MotherModule的行为。如果你想在组织上将子类模型分组到一个文件夹中,那么你当然还可以这样做。只需使用上面单表继承部分末尾概述的相同方法。但我仍然可以将MotherModule保留在 models / concerns 文件夹中。

答案 1 :(得分:3)

尽管ruby实际上没有抽象类,但它足够强大,可以通过在mixin模块上实现self.included来自己实现它。希望这个通用示例能够为您的特定实现提供足够的信息。

module MotherInterface

  def self.included base
    required_class_methods = [:method1, :method2]
    required_instance_methods = [:fizzle, :fazzle]
    required_associations = [:traits, :whatevers]

    required_class_methods.each do |cm|
      raise "MotherInterface: please define .#{cm} class method on host class #{base.name}" unless base.respond_to?(cm)
    end

    required_instance_methods.each do |im|
      raise "MotherInterface: please define ##{im} instance method on host class #{base.name}" unless base.instance_methods.include?(im)
    end

    required_associations.each do |ass|
      raise "MotherInterface: please be sure #{base.name} has a :#{ass} association" unless base.reflections.key?(ass)
    end

    base.send :include, InstanceMethods
    base.extend ClassMethods
  end

  # inherited instance methods
  module InstanceMethods
    def foo
    end

    def bar
    end
  end

  # inherited class methods
  module ClassMethods
    def baz
    end

    def bat
    end
  end

end


class SubClassA < ActiveRecord::Base
  include MotherInterface
  # ... define required methods here ...
end

class SubClassB < ActiveRecord::Base
  include MotherInterface
end

这种方法的一些优点是:

  1. 是的,您仍然可以在技术上实例化mixin,但它实际上并不依赖于活动记录,所以它的味道更像是一个抽象类。
  2. 子类可以定义自己的连接信息。你有两个数据库?不同的列?很酷,没问题。只需适当地实现您的实例方法和内容。
  3. 父母和孩子之间的分界线非常明显。
  4. 但是,也有缺点:

    1. 所有元编程都有点复杂。你必须抽象地思考(HA!)关于如何组织你的代码。
    2. 我可能还有其他优点和缺点,这里有点匆忙。

      现在,就文件位置而言,我建议mixin本身,大概是mother_interface.rb,去除模型文件夹之外的其他位置。

      在config / application.rb中,抛出一行如下:

      config.autoload_paths << File.join(Rails.root, 'app', 'lib')
      

      ...然后你可以创建(rails)/app/lib/mother_interface.rb。真的,你应该这样做,但对你有意义。我不喜欢这个“关注”这个词,而其他人则不喜欢“lib”这个词。所以,请使用您喜欢的任何词语,或者自己制作。

答案 2 :(得分:1)

使用单表继承和少量元编程

# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
  def self.inherited(subclass)
    subclass.include(OnlyChildMethods)
  end

  module OnlyChildMethods
     extend ActiveSupport::Concern
     included do
       def child_method_one
         puts 'hi one'
       end

       def child_method_two
         puts 'hi two'
       end
     end
  end 
end

# app/models/sub_class_a.rb
class SubClassA < MotherClass
   def some_specific_method
      #some code
   end
end

# app/models/sub_class_b.rb
class SubClassB < MotherClass
   def some_specific_method
       #some code
   end
end


mother_class_instance.child_method_one
=> NoMethodError: undefined method 'child_method_one'
sub_class_a_instance.child_method_one
hi one
=> nil