也许我对这应该如何工作的理解是错误的,但是我看到字符串存储在我的数据库中,而我希望它们是 jsonb array
.这是我如何设置:
迁移
t.jsonb :variables, array: true
型号
attribute :variables, :variable, array: true
自定义 ActiveRecord::Type
ActiveRecord::Type.register(:variable, Variable::Type)
自定义变量类型
class Variable::Type < ActiveRecord::Type::Json
include ActiveModel::Type::Helpers::Mutable
# Type casts a value from user input (e.g. from a setter). This value may be a string from the form builder, or a ruby object passed to a setter. There is currently no way to differentiate between which source it came from.
# - value: The raw input, as provided to the attribute setter.
def cast(value)
unless value.nil?
value = Variable.new(value) if !value.kind_of?(Variable)
value
end
end
# Converts a value from database input to the appropriate ruby type. The return value of this method will be returned from ActiveRecord::AttributeMethods::Read#read_attribute. The default implementation just calls #cast.
# - value: The raw input, as provided from the database.
def deserialize(value)
unless value.nil?
value = super if value.kind_of?(String)
value = Variable.new(value) if value.kind_of?(Hash)
value
end
end
因此,从应用程序的角度来看,此方法确实有效。我可以将值设置为
variables = [Variable.new, Variable.new]
它正确地存储在数据库中,并作为 [Variable, Variable]
的数组检索回来.我所关心的,也是这个问题的根源,是在数据库中,变量是使用双转义字符串而不是 json 对象存储的:
{
"{\"token\": \"a\", \"value\": 1, \"default_value\": 1}",
"{\"token\": \"b\", \"value\": 2, \"default_value\": 2}"
}
我希望它们存储的东西更像这样的 json 对象:
{
{"token": "a", "value": 1, "default_value": 1},
{"token": "b", "value": 2, "default_value": 2}
}
这样做的原因是,根据我的理解,如果以 json 格式而不是字符串格式直接从数据库查询此列会更快/更容易。通过 rails 查询将不受影响。
如何让我的 Postgres 数据库通过 Rails 正确存储 jsonb 数组?
最佳答案
所以事实证明,Rails 5 attribute
api 还不完美(并且没有很好的文档记录),并且 Postgres 数组支持引起了一些问题,至少在我想使用它的方式上是这样。我对解决方案使用了相同的方法来解决问题,但我没有告诉 rails 使用我自定义类型的数组,而是使用自定义类型数组。代码胜于 Eloquent :
迁移
t.jsonb :variables, default: []
型号
attribute :variables, :variable_array, default: []
自定义 ActiveRecord::Type
ActiveRecord::Type.register(:variable_array, VariableArrayType)
自定义变量类型
class VariableArrayType < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb
def deserialize(value)
value = super # turns raw json string into array of hashes
if value.kind_of? Array
value.map {|h| Variable.new(h)} # turns array of hashes into array of Variables
else
value
end
end
end
现在,正如预期的那样,db 条目不再存储为字符串,而是存储为可搜索/可索引的
jsonb
。这首歌和舞蹈的全部原因是我可以使用普通的旧 ruby 对象设置 variables
属性......template.variables = [Variable.new(token: "a", default_value: 1), Variable.new(token: "b", default_value: 2)]
...然后将其序列化为数据库中的 jsonb 表示...
[
{"token": "a", "default_value": 1},
{"token": "b", "default_value": 2}
]
...但更重要的是,自动反序列化并重新水化回普通的旧 ruby 对象,准备好与它进行交互。
Template.find(123).variables = [#<Variable:0x87654321 token: "a", default_value: 1>, #<Variable:0x12345678 token: "b", default_value: 2>]
使用旧的
serialize
api 会导致每次保存时写入(有意通过 Rails 架构设计),无论序列化属性是否已更改。由于可以通过多种方式分配属性,通过覆盖 setter/getter 手动完成所有这些操作是不必要的复杂化,这也是更新 attributes
api 的部分原因。
关于ruby-on-rails - 在 Rails 5 中存储 Jsonb 的 Postgres 数组意外转义字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56156323/