ruby Rails jsonb列作为嵌套的`attribute`类型

hmae6n7t  于 2023-08-04  发布在  Ruby
关注(0)|答案(3)|浏览(95)

我在具有以下模式的模型上使用jsonb列:

{
  name: 'abc',
  points: [{
   x: 0,
   y: 2,
  }, {
   x: 2,
   y: 3,
  }],
}

字符串
我想使用嵌套的ActiveModel::attribute方法来(反)序列化数据,以便能够在json上使用ActiveModel验证。
这就是我所尝试的:

# app/models/parent_model.rb
class ParentModel < ApplicationRecord
  attribute :child, ChildModel.new
end

x

# app/models/child_model.rb
class ChildModel < ActiveModel::Type::Value
  include ActiveModel::Model

  attr_accessor :name

  attribute :points, Point.new, array: true

  def cast(value)
    case value
    when String
      decoded = ActiveSupport::JSON.decode(value) rescue nil
      ChildModel.new(decoded) unless decoded.nil?
    when Hash
      ChildModel.new(value)
    when ChildModel
      value
    end
  end

  def serialize(value)
    value&.attributes&.to_json
  end

  def changed_in_place?(raw_old_value, new_value)
    deserialize(raw_old_value) != new_value
  end
end
# app/models/point.rb
class Point < ActiveModel::Type::Value
  include ActiveModel::Model

  attr_accessor :x, :y

  def cast(value)
    case value
    when String
      decoded = ActiveSupport::JSON.decode(value) rescue nil
      Point.new(decoded) unless decoded.nil?
    when Hash
      Point.new(value)
    when Point
      value
    end
  end

  def serialize(value)
    value&.attributes&.to_json
  end

  def changed_in_place?(raw_old_value, new_value)
    deserialize(raw_old_value) != new_value
  end
end

的字符串
这里有一些问题:

  • PointChildModel具有属性limitprecisionscale。他们似乎来自ActiveModel::Type::Value,但我想知道为什么?我应该使用不同的基类吗?
  • child属性似乎作为nil保存到数据库中,即使它是在示例化ParentModel时设置的

在rails中,我想做的事情的正确方法是什么?

fhg3lkii

fhg3lkii1#

我知道这不能回答你的问题...但是我在这里看到了许多与使用jsonb或数组列类型的缺点有关的问题,如果使用了不同的表结构,这些问题是可以避免的。我的建议是:

ParentModel < ApplicationRecord
  has_one :child_model
end

ChildModel < ApplicationRecord
  # fields could be name, x1, x2, y1, y2
  belongs_to :parent_model
end

字符串
你的生活会更轻松!

y53ybaqx

y53ybaqx2#

我现在使用的是attr_json gem,这正是我所需要的。不过,我认为使用“vanilla rails”是可能的。🤷‍♂️

bwitn5fc

bwitn5fc3#

您可能希望使用serialize而不是attributes
例如:

class ParentModel < ApplicationRecord 
  serialize :child, ChildModel
  
  validate :child_valid?

private 
  def child_valid? 
    errors.add(:child, 'is invalid') unless child.valid? 
  end 
end 

class ChildModel
  include ActiveModel::API
  include ActiveModel::Serializers::JSON

  attr_accessor :name
  attr_writer :points

  # add validations
  validate :points_valid?
  
  def initialize(h)
    self.points = h.delete('points')&.map{ |point| Point.new(point)}
    super
  end

  def points 
    @points ||= []
  end 
  
  def self.dump(value)
    value.to_json(include: :points)
  end

  def self.load(string)
    obj = self.new(JSON.parse(string)) 
  end

  def attributes 
    {'name'=> nil}
  end 

private 
  def points_valid?
    points.all?(&:valid?)
  end 

end  

class Point
  include ActiveModel::API
  include ActiveModel::Serializers::JSON
  
  attr_accessor :x,:y

  # add validations

  def attributes 
    {'x'=> nil, 'y' => nil}
  end 
end

字符串

相关问题