Rails STI:如何更改“类型”列的类名和值之间的映射

由于公司规则,我不能使用我们的域名类; 我打算用一个类比来代替。 我有一个名为projects的表,其中有一个名为’type’的列,可能的值为’indoor’和’outdoor’。 具有室内和室外的记录具有明显的function分离,并且作为STI实现非常巧妙地适合。 不幸的是,我无法更改类型名称,也无法在全局命名空间中添加类。 有没有办法为’type’指定不同的值?

注意:我不是要为STI使用不同的列名而不是’type’。 我希望类型的值不同于我的类名。

你可以尝试这样的事情:

class Proyect < ActiveRecord::Base end 

然后是室内课,但有其他名字

 class Other < Proyect class << self def find_sti_class(type_name) type_name = self.name super end def sti_name "Indoor" end end end 

同样适用于户外课程。 您可以在http://apidock.com/rails/ActiveRecord/Base/find_sti_class/class中查看sti_name

这是可能的,但有点复杂。 实质上,当您保存inheritance类的记录时,该方法会向上移动到具有添加的“type”值的父类。

标准定义:

 class Vehicle < ActiveRecord::Base .. def type=(sType) end .. end class Truck < Vehicle # calling save here will trigger a save on a vehicle object with type=Truck end 

在我看来,改变这种情况至关重要。 你可能会遇到其他问题。

我最近发现AR方法变得应该允许你将子对象变形为父对象,或者当文档建议父母进入孩子时,你可能会有一些运气。

 vehicle = Vehicle.new vehicle = vehicle.becomes(Truck) # convert to instance from Truck to Vehicle vehicle.type = "Truck" vehicle.save! 

我自己并没有使用它,但简单地说,您应该能够在保存之前更改type列值。 这可能会导致内置的Active Record查询界面和关联出现一些问题。

此外,您可以执行以下操作:

 class Truck .. def self.model_name "MyNewClassName" end .. end 

但是使用这种方法要注意其余的rails,包括路由和控制器,将模型称为“MyNewClassName”而不是“Truck”。

PS:如果你不能在全局命名空间中添加类,那么为什么不将它们添加到另一个命名空间中呢? 父级和子级可以属于不同的命名空间,也可以属于唯一的命名空间(不是全局的)。

如果只需要从type字段中删除模块名称,则可以使用以下助手模块(基于VAIRIX的答案):

 # lib/util/namespaceless_sti.rb module NamespacelessSti def find_sti_class(type_name) type_name = self.name super end def sti_name self.name.sub(/^.*:/,"") end end 

该模块必须扩展到每个STI基类,例如来自OP描述的Project (想象模型存在于Acme命名空间/模块中)

 # app/models/acme/project.rb require "util/namespaceless_sti" module Acme class Project < ActiveRecord::Base extend NamespacelessSti end end # app/models/acme/indoor.rb module Acme class Indoor < Project # nothing special needs to be done here end end 

这样可以确保projects表中的Acme::Indoor记录在其type字段中具有“室内”。

在查看源代码时,似乎有一个store_full_sti_class选项(我不知道它何时被引入):

config.active_record.store_full_sti_class = false

在Rails 4.2.7中,我摆脱了命名空间模块

在Rails 5中,Attributes API允许我们更改任何列的序列化,包括STI模型的type列,因此:

 # lib/my_sti_type.rb class MyStiType < ActiveRecord::Type::String def cast_value(value) case value when 'indoor'; 'App::CarpetProject' when 'outdoor'; 'App::LawnProject' else super end end def serialize(value) case value when 'App::CarpetProject'; 'indoor' when 'App::LawnProject'; 'outdoor' else super end end def changed_in_place?(original_value_for_database, value) original_value_for_database != serialize(value) end end # config/initializers/types.rb require 'my_sti_type' ActiveRecord::Type.register(:my_sti_type, MyStiType) # app/models/project.rb class Project < ActiveRecord::Base attribute :type, :my_sti_type end 

根据需要替换您自己的类名和字符串匹配/操作。 请参阅此提交以获取示例。

相同的机制也适用于多态belongs_to关联的attributename_type列。

在Rails 5.1.4中,@ VAIRIX的解决方案对我不起作用,因为我有整数类型的inheritance_column,而Rails find_sti_class在第一行有额外的强制转换:

 type_name = base_class.type_for_attribute(inheritance_column).cast(type_name) 

我遇到了演员问题。 即使我准备了type_name到正确的类名。 强制转换需要整数并将任何字符串更改为0.这就是我扩展#compute_type并将store_full_sti_class设置为false的原因:

 class Parent < ApplicationRecord self.table_name = 'PolyTable' self.inheritance_column = 'RecordType' self.store_full_sti_class = false class << self def compute_type(type_name) type_name = case type_name.to_s when "4" "ChildOfType4" ... else type_name end super end end end