aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations/association_collection.rb
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2005-01-15 17:45:16 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2005-01-15 17:45:16 +0000
commit823554eafef9e8ee8fe2788f6231a3e665c2cbbf (patch)
tree6059c8e2c943a7fb45a56bf80cc5786934b70de1 /activerecord/lib/active_record/associations/association_collection.rb
parent62f0512e54d594c4bb6fcb8d16101fdeb87b89e8 (diff)
downloadrails-823554eafef9e8ee8fe2788f6231a3e665c2cbbf.tar.gz
rails-823554eafef9e8ee8fe2788f6231a3e665c2cbbf.tar.bz2
rails-823554eafef9e8ee8fe2788f6231a3e665c2cbbf.zip
Added support for associating unsaved objects #402 [Tim Bates]
Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 [Tim Bates] Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 [Tim Bates] Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 [Tim Bates] Added that if a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last. #402 [Tim Bates] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@417 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib/active_record/associations/association_collection.rb')
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb87
1 files changed, 37 insertions, 50 deletions
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index ca87fae5ff..334cdccc3d 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -1,51 +1,34 @@
module ActiveRecord
module Associations
- class AssociationCollection #:nodoc:
- alias_method :proxy_respond_to?, :respond_to?
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?)/ }
-
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
- @owner = owner
- @options = options
- @association_name = association_name
- @association_class = eval(association_class_name)
- @association_class_primary_key_name = association_class_primary_key_name
- end
-
- def method_missing(symbol, *args, &block)
- load_collection
- @collection.send(symbol, *args, &block)
- end
-
+ class AssociationCollection < AssociationProxy #:nodoc:
def to_ary
- load_collection
- @collection.to_ary
+ load_target
+ @target.to_ary
end
- def respond_to?(symbol, include_priv = false)
- proxy_respond_to?(symbol, include_priv) || [].respond_to?(symbol, include_priv)
+ def reset
+ @target = []
+ @loaded = false
end
- def loaded?
- !@collection.nil?
- end
-
def reload
- @collection = nil
+ reset
end
# Add +records+ to this association. Returns +self+ so method calls may be chained.
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
def <<(*records)
+ result = true
+ load_target
@owner.transaction do
flatten_deeper(records).each do |record|
raise_on_type_mismatch(record)
- insert_record(record)
- @collection << record if loaded?
+ result &&= insert_record(record) unless @owner.new_record?
+ @target << record
end
end
- self
+ result and self
end
alias_method :push, :<<
@@ -54,11 +37,13 @@ module ActiveRecord
# Remove +records+ from this association. Does not destroy +records+.
def delete(*records)
records = flatten_deeper(records)
+ records.each { |record| raise_on_type_mismatch(record) }
+ records.reject! { |record| @target.delete(record) if record.new_record? }
+ return if records.empty?
@owner.transaction do
- records.each { |record| raise_on_type_mismatch(record) }
delete_records(records)
- records.each { |record| @collection.delete(record) } if loaded?
+ records.each { |record| @target.delete(record) }
end
end
@@ -67,20 +52,27 @@ module ActiveRecord
each { |record| record.destroy }
end
- @collection = []
+ @target = []
end
+ def create(attributes = {})
+ # Can't use Base.create since the foreign key may be a protected attribute.
+ record = build(attributes)
+ record.save unless @owner.new_record?
+ record
+ end
+
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
# calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
# and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
def size
- if loaded? then @collection.size else count_records end
+ if loaded? then @target.size else count_records end
end
# Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
# whether the collection is empty, use collection.length.zero? instead of collection.empty?
def length
- load_collection.size
+ load_target.size
end
def empty?
@@ -91,11 +83,14 @@ module ActiveRecord
collection.inject([]) { |uniq_records, record| uniq_records << record unless uniq_records.include?(record); uniq_records }
end
- protected
- def loaded?
- not @collection.nil?
- end
+ def replace(other_array)
+ other_array.each{ |val| raise_on_type_mismatch(val) }
+
+ @target = other_array
+ @loaded = true
+ end
+ protected
def quoted_record_ids(records)
records.map { |record| record.quoted_id }.join(',')
end
@@ -117,22 +112,14 @@ module ActiveRecord
end
private
- def load_collection
- if loaded?
- @collection
- else
- begin
- @collection = find_all_records
- rescue ActiveRecord::RecordNotFound
- @collection = []
- end
- end
- end
-
def raise_on_type_mismatch(record)
raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
end
+ def target_obsolete?
+ false
+ end
+
# Array#flatten has problems with rescursive arrays. Going one level deeper solves the majority of the problems.
def flatten_deeper(array)
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten