我想定义3个类:
MotherClass
(摘要,无法推断)SubClassA
(继承自MotherClass
)SubClassB
(继承自MotherClass
)在Rails中声明它的最佳解决方案是什么?
1。将所有内容放入app / models /
MotherClass < AR::Base
app_models / sub_class_a.rb SubClassA < MotherClass
app / models / sub_class_b.rb SubClassB < MotherClass
优点:实施起来并不复杂
不方便:模特文件夹中的一大堆混乱
2。为两个子类创建模块
MotherClass < AR::Base
app / models / mother_module / sub_class_a.rb中的MotherModule::SubClassA < MotherClass
MotherModule::SubClassB < MotherClass
优势:与解决方案1相同
不方便:使用不同的名称命名MotherModule
和MotherClass
,但它们的意思几乎相同
第3。为3个类创建一个模块
MotherModule::Base < AR::Base
app / models / mother_module / sub_class_a.rb中的MotherModule::SubClassA < MotherModule::Base
MotherModule::SubClassB < MotherModule::Base
优点:非常干净
不方便:需要Base
中的某些功能覆盖(例如table_name)
所以我的问题是:Rails中的最佳实践是什么? - 如何命名我的课程? - 他们的目录是什么?
答案 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.method2
或SubClassB.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的共同组织模式是:
# 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.method2
或SubClassB.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方法的组织简单明了,SubclassA
和SubClassB
是(显然)模型,因为它们包含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
这种方法的一些优点是:
但是,也有缺点:
我可能还有其他优点和缺点,这里有点匆忙。
现在,就文件位置而言,我建议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