module JsonbAccessor module Macro class << self def group_attributes(value_fields, typed_fields) value_fields_hash = process_value_fields(value_fields) typed_fields.each_with_object(nested: {}, typed: value_fields_hash) do |(attribute_name, type_or_nested), grouped_attributes| group = type_or_nested.is_a?(Hash) ? grouped_attributes[:nested] : grouped_attributes[:typed] group[attribute_name] = type_or_nested end end def process_value_fields(value_fields) value_fields.each_with_object({}) do |value_field, hash_for_value_fields| hash_for_value_fields[value_field] = :value end end end module ClassMethods def jsonb_accessor(jsonb_attribute, *value_fields, **typed_fields) all_fields = Macro.group_attributes(value_fields, typed_fields) nested_fields, typed_fields = all_fields.values_at(:nested, :typed) class_namespace = Module.new attribute_namespace = Module.new JsonbAccessor.const_set(name, class_namespace) class_namespace.const_set(jsonb_attribute.to_s.camelize, attribute_namespace) nested_classes = ClassBuilder.generate_nested_classes(attribute_namespace, nested_fields) singleton_class.send(:define_method, "#{jsonb_attribute}_classes") do nested_classes end delegate "#{jsonb_attribute}_classes", to: :class define_method(:initialize_jsonb_attrs) do jsonb_attribute_hash = send(jsonb_attribute) || {} (typed_fields.keys + nested_fields.keys).each do |field| send("#{field}=", jsonb_attribute_hash[field.to_s]) end end after_initialize :initialize_jsonb_attrs jsonb_setters = Module.new typed_fields.each do |field, type| attribute(field.to_s, TypeHelper.fetch(type)) jsonb_setters.instance_eval do define_method("#{field}=") do |value, *args, &block| super(value, *args, &block) new_jsonb_value = (send(jsonb_attribute) || {}).merge(field => attributes[field.to_s]) send("#{jsonb_attribute}=", new_jsonb_value) end end end nested_fields.each do |field, nested_attributes| attribute(field.to_s, TypeHelper.fetch(:value)) jsonb_setters.instance_eval do define_method("#{field}=") do |value| instance_class = nested_classes[field] case value when instance_class instance = instance_class.new(value.attributes) when Hash instance = instance_class.new(value) when nil instance = instance_class.new else raise UnknownValue, "unable to set value '#{value}' is not a hash, `nil`, or an instance of #{instance_class} in #{__method__}" end instance.parent = self new_jsonb_value = (send(jsonb_attribute) || {}).merge(field.to_s => instance.attributes) send("#{jsonb_attribute}=", new_jsonb_value) super(instance) end end end include jsonb_setters end end end end