也许我对应该如何工作的理解是错误的,但是当我期望字符串是jsonb array
时,我看到了存储在数据库中的字符串。这是我的设置方法:
迁移
t.jsonb :variables, array: true
模型
attribute :variables, :variable, array: true
自定义ActiveRecord ::类型
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数据库通过导轨正确存储jsonb数组?
答案 0 :(得分:0)
一种解决方案是仅通过JSON.parse解析变量,将其压入一个空数组中,然后将其分配给该属性。
variables = []
variable = "{\"token\": \"a\", \"value\": 1, \"default_value\": 1}"
variable.class #String
parsed_variable = JSON.parse(variable) #{"token"=>"a", "value"=>1, "default_value"=>1}
parsed_variable.class #Hash
variables.push parsed_variable
答案 1 :(得分:0)
因此,事实证明Rails 5 attribute
api尚不完善(并且没有充分记录),并且Postgres数组支持至少在我要使用它的方式上引起了一些问题。对于解决方案,我使用了相同的方法来解决问题,但是我不是在告诉Rails使用自定义类型的数组,而是使用自定义类型的数组。代码胜于雄辩:
迁移
t.jsonb :variables, default: []
模型
attribute :variables, :variable_array, default: []
自定义ActiveRecord ::类型
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
现在,正如预期的那样,数据库条目不再存储为字符串,而是可搜索/可索引的jsonb
。进行这种歌舞的全部原因是,我可以使用普通的旧红宝石对象设置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}
]
...但是更重要的是,它会自动反序列化并重新水化为普通的旧红宝石对象,以便我与之交互。
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。
答案 2 :(得分:0)
如果它对其他人有帮助,Rails 希望您提供可能的密钥以在控制器中允许使用强参数:
def controller_params
params.require(:parent_key)
.permit(
jsonb_field: [:allowed_key1, :allowed_key2, :allowed_key3]
)
end