Skip to content

Instantly share code, notes, and snippets.

@Zequez
Created March 28, 2012 04:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Zequez/2223565 to your computer and use it in GitHub Desktop.
Save Zequez/2223565 to your computer and use it in GitHub Desktop.
Allow existing associations in accepts_nested_attributes_for
module ActiveRecord::NestedAttributes
module ClassMethods
def accepts_nested_attributes_for(*attr_names)
options = { :allow_destroy => false, :update_only => false }
options.update(attr_names.extract_options!)
# Modified stuff
#options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only, :allow_existing)
# /Modified stuff
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
attr_names.each do |association_name|
if reflection = reflect_on_association(association_name)
reflection.options[:autosave] = true
add_autosave_association_callbacks(reflection)
nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
self.nested_attributes_options = nested_attributes_options
type = (reflection.collection? ? :collection : :one_to_one)
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
# end
class_eval " if method_defined?(:#{association_name}_attributes=)
remove_method(:#{association_name}_attributes=)
end
def #{association_name}_attributes=(attributes)
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
end
", __FILE__, __LINE__ + 1
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
end
end
end
end
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {})
options = self.nested_attributes_options[association_name]
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
end
if options[:limit] && attributes_collection.size > options[:limit]
raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
end
if attributes_collection.is_a? Hash
keys = attributes_collection.keys
attributes_collection = if keys.include?('id') || keys.include?(:id)
Array.wrap(attributes_collection)
else
attributes_collection.values
end
end
association = association(association_name)
# Modified stuff
#existing_records = if association.loaded?
# association.target
#else
# attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
# attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids)
#end
existing_records = if options[:allow_existing] or not association.loaded?
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
scope = options[:allow_existing] ? association.target_scope : association.scoped
scope.where(association.klass.primary_key => attribute_ids)
else
association.target
end
# / Modified stuff
attributes_collection.each do |attributes|
attributes = attributes.with_indifferent_access
if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
end
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
unless association.loaded? || call_reject_if(association_name, attributes)
# Make sure we are operating on the actual object which is in the association's
# proxy_target array (either by finding it, or adding it if not found)
target_record = association.target.detect { |record| record == existing_record }
if target_record
existing_record = target_record
else
association.add_to_target(existing_record)
end
end
if !call_reject_if(association_name, attributes)
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts)
end
elsif assignment_opts[:without_protection]
association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
raise_nested_attributes_record_not_found(association_name, attributes['id'])
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment