由于公司规则,我不能使用我们的域名类;我打算用一个类比来代替。我有一个名为projects的表,其中有一个名为'type'的列,可能的值为'indoor'和'outdoor'。具有室内和室外的记录具有明显的功能分离,并且作为STI实现非常巧妙地适合。不幸的是,我无法更改类型名称,也无法在全局命名空间中添加类。有没有办法为'type'指定不同的值?
注意:我不是要为STI使用不同的列名而不是'type'。我希望类型的值不同于我的类名。
由于公司规则,我不能使用我们的域名类;我打算用一个类比来代替。我有一个名为projects的表,其中有一个名为'type'的列,可能的值为'indoor'和'outdoor'。具有室内和室外的记录具有明显的功能分离,并且作为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
同样适用于户外课程。 你可以检查sti_name http://apidock.com/rails/ActiveRecord/Base/find_sti_class/class
这是可能的,但有点复杂。实质上,当您保存继承类的记录时,该方法会向上移动到具有添加的“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基类,例如 Project
来自OP的描述(想象模特住在 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
这确保了 Acme::Indoor
记录 projects
桌子上会有“室内”字样 type
领域。
在查看源代码时似乎有一个 store_full_sti_class
选项(我不知道它何时推出):
config.active_record.store_full_sti_class = false
在Rails 4.2.7中,我摆脱了命名空间模块
在Rails 5中,Attributes API允许我们更改任何列的序列化,包括 type
STI模型列,因此:
# 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
根据需要替换您自己的类名和字符串匹配/操作。看到 这个提交举个例子。
同样的机制也适用于 attributename_type
多态belongs_to关联的列。
在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