diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index e3122d1..1b7f0c6 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -227,11 +227,18 @@ module ActiveRecord marked_for_destruction? end + # See ActionView::Helpers::FormHelper::fields_for for more info. + def _unlink + # TODO: should return true when validation of a form fails and + # a record has been marked for unlinking. + false + end + private # Attribute hash keys that should not be assigned as normal attributes. # These hash keys are nested attributes implementation details. - UNASSIGNABLE_KEYS = %w{ id _delete } + UNASSIGNABLE_KEYS = %w{ id _delete _unlink } # Assigns the given attributes to the association. # @@ -297,8 +304,19 @@ module ActiveRecord unless reject_new_record?(association_name, attributes) send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS)) end - elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s } - assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy) + else + existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s } + if attributes.has_key?("_unlink") + # here :autosave doesn't work as I would have expected + if has_unlink_flag?(attributes) + send(association_name).delete(existing_record) + elsif existing_record.nil? + existing_record = self.class.reflections[association_name].klass.find(attributes['id'].to_s.to_i) + send(association_name).push(existing_record) if existing_record + end + elsif existing_record + assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy) + end end end end @@ -318,11 +336,17 @@ module ActiveRecord ConnectionAdapters::Column.value_to_boolean hash['_delete'] end + # Determines if a hash contains a truthy _unlink key. + def has_unlink_flag?(hash) + ConnectionAdapters::Column.value_to_boolean hash['_unlink'] + end + # Determines if a new record should be build by checking for - # has_delete_flag? or if a :reject_if proc exists for this - # association and evaluates to +true+. + # has_delete_flag? or has_unlink_flag? or if a :reject_if proc + # exists for this association and evaluates to +true+. def reject_new_record?(association_name, attributes) has_delete_flag?(attributes) || + has_unlink_flag?(attributes) || self.class.reject_new_nested_attributes_procs[association_name].try(:call, attributes) end end