Background: I'm trying to refactor my code after reading Practical Object Oriented Design in Ruby (it's awesome), and in doing so, I want to introduce some more models that encapsulate responsibility, rather than have a single large file with logic (and case statements, for that).
Problem: To simplify the problem statement, I have a model Rule
that "has many" RuleConditions
. However, there is only one table in the database for rules. In it, I have a column for conditions that's of type jsonb
(based on the complications of a RuleCondition
). But I can't seem to accomplish this. Specifically I can't figure out how to instantiate a model with a nested model, and expect ActiveRecord to know how to convert the model to jsonb, and perhaps from the table back to a nested model. I also don't know if I can define a has_many
relationship without a table backing it using ActiveRecord.
What I expect:
I expect that that there should be some flow (defined by a mix of ActiveRecord and ActiveModel) that would make this flow possible
- Get params for a
Rule
. - Create a new array of
RuleConditions
from a subset of the params for a rule. - Do
Rule.new(rule)
where rule contains :conditions =>RuleCondition
- Do
rule.save!
- At some point later, fetch the rule from the table, and expect it to rebuild a
Rule
with the nestedRuleConditions
model from theconditions
attribute.
What I've tried:
What I thought would get me halfway there was the serialize, :conditions, JSON
, but it struggles to serialize my object. After that, I really don't know. I've played around with ActiveModel::Conversion as well. So I just need some guidance.
And, to be perfectly clear, calling as_json
on my RuleCondition
works like I expect it to (prints out the same JSON that used to be stored in the Rule
model and the database before attempting a refactor). So it's maybe that I don't understand serialize
(since it's supposed to YAML unless otherwise, I think the encoding is different than just "match my column type")
Edit:
Currently I have something like (barebones, 0 validations / associations)
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
Now, with the new model that's defined as
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
I'm thinking I need do this
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
But this doesn't work, for (exactly) this reason:
18:13:52 web.1 | SQL (10.7ms) INSERT INTO "rules" ("name", "conditions", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "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: ERROR: invalid input syntax for type json 18:13:52 web.1 | DETAIL: Token "#" is invalid. 18:13:52 web.1 | CONTEXT: JSON data, line 1: #... 18:13:52 web.1 | : INSERT INTO "rules" ("name", "conditions", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" 18:13:52 web.1 | (0.5ms) ROLLBACK