Rails嵌套的模型在没有自己的表的情况下往返数据库的转换

时间:2018-12-16 02:52:34

标签: ruby-on-rails ruby database activerecord activemodel

背景:阅读Practical Object Oriented Design in Ruby后,我试图重构代码(这很棒),为此,我想引入更多封装责任的模型,而不是使用一个逻辑大文件(和案例说明)。

问题:为了简化问题陈述,我有一个模型“ Rule”,其中“有很多” RuleConditions。但是,数据库中只有一个表用于规则。在其中,我有一栏针对类型为jsonb的条件(基于RuleCondition的复杂性)。但我似乎无法实现这一目标。具体来说,我无法弄清楚如何使用嵌套模型实例化模型,并且希望ActiveRecord知道如何将模型转换为jsonb,甚至可能从表转换回嵌套模型。我也不知道是否可以在没有表使用ActiveRecord支持的情况下定义has_many关系。

我期望的是

我希望应该有一些流程(由ActiveRecord和ActiveModel混合定义),使该流程成为可能

  1. 获取Rule的参数。
  2. 从一个新的RuleConditions数组中创建一个 规则参数的子集。
  3. 在规则包含:conditions => Rule.new(rule)
  4. 的情况下执行RuleCondition
  5. 执行rule.save!
  6. 稍后,从表中获取规则,并期望它使用来自Rule属性的嵌套RuleConditions模型重建conditions

我尝试过的事情:

我原本以为我会进入serialize, :conditions, JSON的途中,但是要序列化我的对象很困难。在那之后,我真的不知道。我也玩过ActiveModel :: Conversion。所以我只需要一些指导。

而且,非常清楚,在我的as_json上调用RuleCondition就像我期望的那样(打印出以前存储在Rule模型中的JSON,数据库,然后尝试进行重构)。因此,可能是我不了解serialize(因为除非另有说明,否则应该使用YAML,因此我认为编码不同于“匹配我的列类型”)

编辑:

当前我有类似(准系统,0个验证/关联)

class Rule < ActiveRecord::Base
end

class RuleController < ApplicationController

    def create
        rule = Rule.new(rule_params[:rule]) # conditions are just an attribute in the params
        rule.save
    end
end

现在,使用定义为的新模型

class RuleCondition
    include ActiveModel::Model # (what I'm currently doing to get some of the behavior of a model without the persistence / table backing it, I think) 
    attr_accessor :noun, :subnoun # etc
end

我认为我需要这样做

def create
    rule = rule_params[:rule]
    rule["conditions"] = rule["conditions"].map do |c|
        RuleCondition.new(c)
    end
    true_rule = Rule.new(rule)
    true_rule.save!
end

但这是行不通的,因为(完全)这个原因:

18:13:52 web.1 | SQL(10.7ms)插入“规则”(“名称”,“条件”,“ created_at”,“ updated_at”)值($ 1,$ 2,$ 3,$ 4)返回“ id” [[[“ name”,“ wefw” ],[“ conditions”,“ {#}”],[“ created_at”,“ 2018-12-16 02:13:52.938849”],[“ updated_at”,“ 2018-12-16 02:13:52.938849” ]] 18:13:52 web.1 | PG :: InvalidTextRepresentation:错误:json类型的输入语法无效 18:13:52 web.1 | 详细信息:令牌“#”无效。 18:13:52 web.1 |上下文:JSON数据,第1行:#... 18:13:52 web.1 | :插入“规则”(“名称”,“条件”,“ created_at”,“ updated_at”)值($ 1,$ 2,$ 3,$ 4)返回“ id” 18:13:52 web.1 | (0.5ms)回滚

1 个答案:

答案 0 :(得分:2)

  

请记住,数据库适配器会处理某些序列化任务   为了你。例如:PostgreSQL中的json和jsonb类型将是   在JSON对象/数组语法和Ruby Hash或Array之间转换   透明对象。在这种情况下,无需使用序列化。
  -api.rubyonrails.org

请勿将serialize与本机JSON / JSONB列一起使用。它的意思是与字符串列一起用作穷人替代。

您试图做的事情实际上超出了ActiveRecord的范围-AR是围绕关系模型构建的,其中模型与表相对应。而且,您不能指望AR会有任何规定将JSONB列解组为基本标量类型以外的任何内容。我会考虑,相对于为关系创建一个单独的表,您所做的工作是否真的值得您付出努力?

使用ActiveModel :: Model可以使您的模型走上正确的轨道,它将为您的模型提供与常规模型相同的行为,但是您应该看看ActiveRecord handles nested attributes的情况:

class Rule < ApplicationRecord
  def conditions_attributes=(attributes)
    attributes.each do |a|
      # you would need to implement this method
      unless RuleCondition.reject_attributes?(a)
        self.conditions << RuleCondition.new(c)
      end
    end
  end
end

您可以通过创建设置器/获取器来模仿关联的其他方面。

但是再一次,您可以只创建一个带有JSONB列和一对多或m2m关联的rule_conditions表,而实际上将您的时间花在生产上。