Module: AttrJson::NestedAttributes

Extended by:
ActiveSupport::Concern
Defined in:
lib/attr_json/nested_attributes.rb,
lib/attr_json/nested_attributes/writer.rb,
lib/attr_json/nested_attributes/builder.rb,
lib/attr_json/nested_attributes/multiparameter_attribute_writer.rb

Overview

The implementation is based on ActiveRecord::NestedAttributes, from https://github.com/rails/rails/blob/a45f234b028fd4dda5338e5073a3bf2b8bf2c6fd/activerecord/lib/active_record/nested_attributes.rb

Re-used, and customized/overrode methods to match our implementation. Copied over some implementation so we can use in ActiveModel's that original isn't compatible with. The original is pretty well put together and has had very low churn history.

Much of the AR implementation, copied, just works, if we define '#{attribute_name}_attributes=' methods that work. That's mostly what we have to do here.

Unlike AR, we try to put most of our implementation in seperate implementation helper instances, instead of adding a bazillion methods to the model itself.

Defined Under Namespace

Classes: Builder, MultiparameterAttributeWriter, Writer

Class Method Summary collapse

Class Method Details

.attr_json_accepts_nested_attributes_for(define_build_method: true, reject_if: nil, limit: nil) ⇒ Object

Much like ActiveRecord accepts_nested_attributes_for, but used with embedded AttrJson::Model-type attributes (single or array). See doc page on Forms support.

Note some AR options are not supported.

  • allow_destroy, no such option. Effectively always true, doesn't make sense to try to gate this with our implementation.
  • update_only, no such option. Not really relevant to this architecture where you're embedded models have no independent existence.

If called on an array of 'primitive' (not AttrJson::Model) objects, it will do a kind of weird thing where it creates an #{attribute_name}_attributes= method that does nothing but filter out empty strings and nil values. This can be convenient in hackily form handling array of primitives, see guide doc on forms.

Parameters:

  • define_build_method (Boolean) (defaults to: true)

    Default true, provide build_attribute_name method that works like you expect. Cocoon, for example, requires this. When true, you will get model.build_#{attr_name} methods. For array attributes, the attr_name will be singularized, as AR does.

  • reject_if (Symbol, Proc) (defaults to: nil)

    Allows you to specify a Proc or a Symbol pointing to a method that checks whether a record should be built for a certain attribute hash. Much like in AR accepts_nested_attributes_for.

  • limit (Integer, Proc, Symbol) (defaults to: nil)

    Allows you to specify the maximum number of associated records that can be processed with the nested attributes. Much like AR accepts_nested_attributes for.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/attr_json/nested_attributes.rb', line 49

def attr_json_accepts_nested_attributes_for(*attr_names)
  options = { define_build_method: true }
  options.update(attr_names.extract_options!)
  options.assert_valid_keys(:reject_if, :limit, :define_build_method)
  options[:reject_if] = ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank

  unless respond_to?(:nested_attributes_options)
    # Add it when we're in a AttrJson::Model.  In an ActiveRecord::Base we'll just use the
    # existing one, it'll be okay.
    # https://github.com/rails/rails/blob/c14deceb9f36f82cd5ca3db214d85e1642eb0bfd/activerecord/lib/active_record/nested_attributes.rb#L16
    class_attribute :nested_attributes_options, instance_writer: false
    self.nested_attributes_options ||= {}
  end

  attr_names.each do |attr_name|
    attr_def = attr_json_registry[attr_name]

    unless attr_def
      raise ArgumentError, "No attr_json found for name '#{attr_name}'. Has it been defined yet?"
    end

    unless attr_def.array_type? || attr_def.single_model_type?
      raise TypeError, "attr_json_accepts_nested_attributes_for is only for array or nested model types; `#{attr_name}` is type #{attr_def.type.type.inspect}"
    end

    # We're sharing AR class attr in an AR, or using our own in a Model.
    nested_attributes_options = self.nested_attributes_options.dup
    nested_attributes_options[attr_name.to_sym] = options
    self.nested_attributes_options = nested_attributes_options

    _attr_jsons_module.module_eval do
      if method_defined?(:"#{attr_name}_attributes=")
        remove_method(:"#{attr_name}_attributes=")
      end
      define_method "#{attr_name}_attributes=" do |attributes|
        Writer.new(self, attr_name).assign_nested_attributes(attributes)
      end
    end

    # No build method for our wacky array of primitive type.
    if options[:define_build_method] && !attr_def.array_of_primitive_type?
      _attr_jsons_module.module_eval do
        build_method_name = "build_#{attr_name.to_s.singularize}"
        if method_defined?(build_method_name)
          remove_method(build_method_name)
        end
        define_method build_method_name do |params = {}|
          Builder.new(self, attr_name).build(params)
        end
      end
    end
  end
end