Module: ActiveModel::Datastore::NestedAttr
- Extended by:
- ActiveSupport::Concern
- Includes:
- Model
- Included in:
- ActiveModel::Datastore
- Defined in:
- lib/active_model/datastore/nested_attr.rb
Overview
ActiveModel Datastore Nested Attributes
Adds support for nested attributes to ActiveModel. Heavily inspired by Rails ActiveRecord::NestedAttributes.
Nested attributes allow you to save attributes on associated records along with the parent. It's used in conjunction with fields_for to build the nested form elements.
See Rails ActionView::Helpers::FormHelper::fields_for for more info.
NOTE: Unlike ActiveRecord, the way that the relationship is modeled between the parent and child is not enforced. With NoSQL the relationship could be defined by any attribute, or with denormalization exist within the same entity. This library provides a way for the objects to be associated yet saved to the datastore in any way that you choose.
You enable nested attributes by defining an :attr_accessor on the parent with the pluralized
name of the child model.
Nesting also requires that a <association_name>_attributes= writer method is defined in your
parent model. If an object with an association is instantiated with a params hash, and that
hash has a key for the association, Rails will call the <association_name>_attributes=
method on that object. Within the writer method call assign_nested_attributes, passing in
the association name and attributes.
Let's say we have a parent Recipe with Ingredient children.
Start by defining within the Recipe model:
- an attr_accessor of
:ingredients - a writer method named
ingredients_attributes= - the
validates_associatedmethod can be used to validate the nested objects
Example:
class Recipe
attr_accessor :ingredients
validates :ingredients, presence: true
validates_associated :ingredients
def ingredients_attributes=(attributes)
assign_nested_attributes(:ingredients, attributes)
end
end
You may also set a :reject_if proc to silently ignore any new record hashes if they fail to
pass your criteria. For example:
class Recipe
def ingredients_attributes=(attributes)
reject_proc = proc { |attributes| attributes['name'].blank? }
assign_nested_attributes(:ingredients, attributes, reject_if: reject_proc)
end
end
Alternatively, :reject_if also accepts a symbol for using methods:
class Recipe
def ingredients_attributes=(attributes)
reject_proc = proc { |attributes| attributes['name'].blank? }
assign_nested_attributes(:ingredients, attributes, reject_if: reject_recipes)
end
def reject_recipes(attributes)
attributes['name'].blank?
end
end
Within the parent model valid? will validate the parent and associated children and
nested_models will return the child objects. If the nested form submitted params contained
a truthy _destroy key, the appropriate nested_models will have marked_for_destruction set
to True.
Created by Bryce McLean on 2016-12-06.
Defined Under Namespace
Modules: ClassMethods Classes: AssociatedValidator
Constant Summary collapse
- UNASSIGNABLE_KEYS =
%w[id _destroy].freeze
Instance Method Summary collapse
-
#assign_nested_attributes(association_name, attributes, options = {}) ⇒ Object
Assigns the given nested child attributes.
- #mark_for_destruction ⇒ Object
- #marked_for_destruction? ⇒ Boolean
- #nested_attributes? ⇒ Boolean
- #nested_errors ⇒ Object
- #nested_model_class_names ⇒ Object
-
#nested_models ⇒ Object
For each attribute name in nested_attributes extract and return the nested model objects.
Instance Method Details
#assign_nested_attributes(association_name, attributes, options = {}) ⇒ Object
Assigns the given nested child attributes.
Attribute hashes with an :id value matching an existing associated object will update
that object. Hashes without an :id value will build a new object for the association.
Hashes with a matching :id value and a :_destroy key set to a truthy value will mark
the matched object for destruction.
Pushes a key of the association name onto the parent object's nested_attributes attribute.
The nested_attributes can be used for determining when the parent has associated children.
The following example will update the amount of the ingredient with ID 1, build a new associated ingredient with the amount of 45, and mark the associated ingredient with ID 2 for destruction.
assign_nested_attributes(:ingredients, {
'0' => { id: '1', amount: '123' },
'1' => { amount: '45' },
'2' => { id: '2', _destroy: true }
})
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/active_model/datastore/nested_attr.rb', line 157 def assign_nested_attributes(association_name, attributes, = {}) attributes = validate_attributes(attributes) association_name = association_name.to_sym send("#{association_name}=", []) if send(association_name).nil? attributes.each_value do |params| if params['id'].blank? unless reject_new_record?(params, ) new = association_name.to_c.new(params.except(*UNASSIGNABLE_KEYS)) send(association_name).push(new) end else existing = send(association_name).detect { |record| record.id.to_s == params['id'].to_s } assign_to_or_mark_for_destruction(existing, params) end end (self.nested_attributes ||= []).push(association_name) end |
#mark_for_destruction ⇒ Object
87 88 89 |
# File 'lib/active_model/datastore/nested_attr.rb', line 87 def mark_for_destruction @marked_for_destruction = true end |
#marked_for_destruction? ⇒ Boolean
91 92 93 |
# File 'lib/active_model/datastore/nested_attr.rb', line 91 def marked_for_destruction? @marked_for_destruction end |
#nested_attributes? ⇒ Boolean
95 96 97 |
# File 'lib/active_model/datastore/nested_attr.rb', line 95 def nested_attributes? nested_attributes.is_a?(Array) && !nested_attributes.empty? end |
#nested_errors ⇒ Object
114 115 116 117 118 119 120 121 122 |
# File 'lib/active_model/datastore/nested_attr.rb', line 114 def nested_errors errors = [] if nested_attributes? nested_attributes.each do |attr| send(attr.to_sym).each { |child| errors << child.errors } end end errors end |
#nested_model_class_names ⇒ Object
108 109 110 111 112 |
# File 'lib/active_model/datastore/nested_attr.rb', line 108 def nested_model_class_names entity_kinds = [] nested_models.each { |x| entity_kinds << x.class.name } if nested_attributes? entity_kinds.uniq end |
#nested_models ⇒ Object
For each attribute name in nested_attributes extract and return the nested model objects.
102 103 104 105 106 |
# File 'lib/active_model/datastore/nested_attr.rb', line 102 def nested_models model_entities = [] nested_attributes.each { |attr| model_entities << send(attr.to_sym) } if nested_attributes? model_entities.flatten end |