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_associated
method 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
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 }
})
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/active_model/datastore/nested_attr.rb', line 155 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
85 86 87 |
# File 'lib/active_model/datastore/nested_attr.rb', line 85 def mark_for_destruction @marked_for_destruction = true end |
#marked_for_destruction? ⇒ Boolean
89 90 91 |
# File 'lib/active_model/datastore/nested_attr.rb', line 89 def marked_for_destruction? @marked_for_destruction end |
#nested_attributes? ⇒ Boolean
93 94 95 |
# File 'lib/active_model/datastore/nested_attr.rb', line 93 def nested_attributes? nested_attributes.is_a?(Array) && !nested_attributes.empty? end |
#nested_errors ⇒ Object
112 113 114 115 116 117 118 119 120 |
# File 'lib/active_model/datastore/nested_attr.rb', line 112 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
106 107 108 109 110 |
# File 'lib/active_model/datastore/nested_attr.rb', line 106 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.
100 101 102 103 104 |
# File 'lib/active_model/datastore/nested_attr.rb', line 100 def nested_models model_entities = [] nested_attributes.each { |attr| model_entities << send(attr.to_sym) } if nested_attributes? model_entities.flatten end |