aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/aggregations.rb45
-rw-r--r--activerecord/lib/active_record/association_preload.rb48
-rw-r--r--activerecord/lib/active_record/associations.rb23
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb55
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb18
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb23
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb68
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb33
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb29
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb41
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb4
-rw-r--r--activerecord/lib/active_record/base.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb6
-rw-r--r--activerecord/lib/active_record/fixtures.rb6
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb14
-rw-r--r--activerecord/lib/active_record/transactions.rb18
-rw-r--r--activerecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb11
18 files changed, 212 insertions, 239 deletions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 0bc26cc672..90d3b58c78 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -220,37 +220,32 @@ module ActiveRecord
private
def reader_method(name, class_name, mapping, allow_nil, constructor)
- module_eval do
- define_method(name) do
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
- object = constructor.respond_to?(:call) ?
- constructor.call(*attrs) :
- class_name.constantize.send(constructor, *attrs)
- @aggregation_cache[name] = object
- end
- @aggregation_cache[name]
+ define_method(name) do
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
+ attrs = mapping.collect {|pair| read_attribute(pair.first)}
+ object = constructor.respond_to?(:call) ?
+ constructor.call(*attrs) :
+ class_name.constantize.send(constructor, *attrs)
+ @aggregation_cache[name] = object
end
+ @aggregation_cache[name]
end
-
end
def writer_method(name, class_name, mapping, allow_nil, converter)
- module_eval do
- define_method("#{name}=") do |part|
- if part.nil? && allow_nil
- mapping.each { |pair| self[pair.first] = nil }
- @aggregation_cache[name] = nil
- else
- unless part.is_a?(class_name.constantize) || converter.nil?
- part = converter.respond_to?(:call) ?
- converter.call(part) :
- class_name.constantize.send(converter, part)
- end
-
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
- @aggregation_cache[name] = part.freeze
+ define_method("#{name}=") do |part|
+ if part.nil? && allow_nil
+ mapping.each { |pair| self[pair.first] = nil }
+ @aggregation_cache[name] = nil
+ else
+ unless part.is_a?(class_name.constantize) || converter.nil?
+ part = converter.respond_to?(:call) ?
+ converter.call(part) :
+ class_name.constantize.send(converter, part)
end
+
+ mapping.each { |pair| self[pair.first] = part.send(pair.last) }
+ @aggregation_cache[name] = part.freeze
end
end
end
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 68aaff175a..cba4bab3ef 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -165,10 +165,8 @@ module ActiveRecord
end
id_to_record_map.each do |id, records|
- next if seen_keys.include?(id.to_s)
- records.each do |record|
- record.send(:association_proxy, reflection_name).target = nil
- end
+ next if seen_keys.include?(id)
+ add_preloaded_record_to_collection(records, reflection_name, nil)
end
end
@@ -177,23 +175,18 @@ module ActiveRecord
# <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
# and +ids+ is an Array of record IDs.
def construct_id_map(records, primary_key=nil)
- id_to_record_map = {}
- ids = []
- records.each do |record|
+ records.group_by do |record|
primary_key ||= record.class.primary_key
- ids << record[primary_key]
- mapped_records = (id_to_record_map[ids.last.to_s] ||= [])
- mapped_records << record
+ record[primary_key].to_s
end
- ids.uniq!
- return id_to_record_map, ids
end
def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
left = reflection.klass.arel_table
- id_to_record_map, ids = construct_id_map(records)
+ id_to_record_map = construct_id_map(records)
+
records.each {|record| record.send(reflection.name).loaded}
options = reflection.options
@@ -222,12 +215,11 @@ module ActiveRecord
klass = associated_records_proxy.klass
- associated_records(ids) { |some_ids|
+ associated_records(id_to_record_map.keys) { |some_ids|
method = in_or_equal(some_ids)
- conditions = right[reflection.foreign_key].send(*method)
- conditions = custom_conditions.inject(conditions) do |ast, cond|
- ast.and cond
- end
+ conditions = right.create_and(
+ [right[reflection.foreign_key].send(*method)] +
+ custom_conditions)
relation = associated_records_proxy.where(conditions)
klass.connection.select_all(relation.arel.to_sql, 'SQL', relation.bind_values)
@@ -242,12 +234,10 @@ module ActiveRecord
def preload_has_one_association(records, reflection, preload_options={})
return if records.first.send(:association_proxy, reflection.name).loaded?
- id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
+ id_to_record_map = construct_id_map(records, reflection.options[:primary_key])
options = reflection.options
- records.each do |record|
- record.send(:association_proxy, reflection.name).target = nil
- end
+ add_preloaded_record_to_collection(records, reflection.name, nil)
if options[:through]
through_records = preload_through_records(records, reflection, options[:through])
@@ -258,7 +248,7 @@ module ActiveRecord
source = reflection.source_reflection.name
through_records.first.class.preload_associations(through_records, source)
if through_reflection.macro == :belongs_to
- id_to_record_map = construct_id_map(records, through_primary_key).first
+ id_to_record_map = construct_id_map(records, through_primary_key)
through_primary_key = through_reflection.klass.primary_key
end
@@ -268,7 +258,7 @@ module ActiveRecord
end
end
else
- set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.foreign_key)
+ set_association_single_records(id_to_record_map, reflection.name, find_associated_records(id_to_record_map.keys, reflection, preload_options), reflection.foreign_key)
end
end
@@ -277,7 +267,7 @@ module ActiveRecord
options = reflection.options
foreign_key = reflection.through_reflection_foreign_key
- id_to_record_map, ids = construct_id_map(records, foreign_key || reflection.options[:primary_key])
+ id_to_record_map = construct_id_map(records, foreign_key || reflection.options[:primary_key])
records.each {|record| record.send(reflection.name).loaded}
if options[:through]
@@ -293,7 +283,7 @@ module ActiveRecord
end
else
- set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
+ set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(id_to_record_map.keys, reflection, preload_options),
reflection.foreign_key)
end
end
@@ -391,7 +381,7 @@ module ActiveRecord
conditions << table["#{interface}_type"].eq(base_class.sti_name)
end
- conditions += append_conditions(reflection, preload_options)
+ conditions.concat append_conditions(reflection, preload_options)
find_options = {
:select => preload_options[:select] || options[:select] || table[Arel.star],
@@ -403,9 +393,7 @@ module ActiveRecord
associated_records(ids) do |some_ids|
method = in_or_equal(some_ids)
- where = conditions.inject(table[key].send(*method)) do |ast, cond|
- ast.and cond
- end
+ where = table.create_and(conditions + [table[key].send(*method)])
reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => where)).to_a
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index a03d1bbb06..891ac52f8a 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -119,6 +119,7 @@ module ActiveRecord
# These classes will be loaded when associations are created.
# So there is no need to eager load them.
autoload :AssociationCollection, 'active_record/associations/association_collection'
+ autoload :SingularAssociation, 'active_record/associations/singular_association'
autoload :AssociationProxy, 'active_record/associations/association_proxy'
autoload :ThroughAssociation, 'active_record/associations/through_association'
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
@@ -208,7 +209,7 @@ module ActiveRecord
# other=(other) | X | X | X
# build_other(attributes={}) | X | | X
# create_other(attributes={}) | X | | X
- # other.create!(attributes={}) | | | X
+ # create_other!(attributes={}) | X | | X
#
# ===Collection associations (one-to-many / many-to-many)
# | | | has_many
@@ -1042,6 +1043,9 @@ module ActiveRecord
# Returns a new object of the associated type that has been instantiated
# with +attributes+, linked to this object through a foreign key, and that
# has already been saved (if it passed the validation).
+ # [create_association!(attributes = {})]
+ # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
+ # if the record is invalid.
#
# (+association+ is replaced with the symbol passed as the first argument, so
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
@@ -1053,6 +1057,7 @@ module ActiveRecord
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
+ # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
#
# === Options
#
@@ -1157,6 +1162,9 @@ module ActiveRecord
# Returns a new object of the associated type that has been instantiated
# with +attributes+, linked to this object through a foreign key, and that
# has already been saved (if it passed the validation).
+ # [create_association!(attributes = {})]
+ # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
+ # if the record is invalid.
#
# (+association+ is replaced with the symbol passed as the first argument, so
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
@@ -1168,6 +1176,7 @@ module ActiveRecord
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
+ # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
# The declaration can also include an options hash to specialize the behavior of the association.
#
# === Options
@@ -1292,12 +1301,6 @@ module ActiveRecord
# end
# end
#
- # Deprecated: Any additional fields added to the join table will be placed as attributes when
- # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join
- # tables with additional attributes will be marked as readonly (because we can't save changes
- # to the additional attributes). It's strongly recommended that you upgrade any
- # associations with attributes to a real join model (see introduction).
- #
# Adds the following methods for retrieval and query:
#
# [collection(force_reload = false)]
@@ -1533,10 +1536,10 @@ module ActiveRecord
def association_constructor_methods(reflection)
constructors = {
- "build_#{reflection.name}" => "build",
- "create_#{reflection.name}" => "create"
+ "build_#{reflection.name}" => "build",
+ "create_#{reflection.name}" => "create",
+ "create_#{reflection.name}!" => "create!"
}
- constructors["create_#{reflection.name}!"] = "create!" if reflection.macro == :has_one
constructors.each do |name, proxy_name|
redefine_method(name) do |*params|
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index e65ef2b768..24fb49a65d 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -349,35 +349,19 @@ module ActiveRecord
end
def load_target
- if !@owner.new_record? || foreign_key_present?
+ if (!@owner.new_record? || foreign_key_present?) && !loaded?
+ targets = []
+
begin
- unless loaded?
- if @target.is_a?(Array) && @target.any?
- @target = find_target.map do |f|
- i = @target.index(f)
- if i
- @target.delete_at(i).tap do |t|
- keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
- # FIXME: this call to attributes causes many NoMethodErrors
- attributes = f.attributes
- (attributes.keys - keys).each do |k|
- t.send("#{k}=", attributes[k])
- end
- end
- else
- f
- end
- end + @target
- else
- @target = find_target
- end
- end
+ targets = find_target
rescue ActiveRecord::RecordNotFound
reset
end
+
+ @target = merge_target_lists(targets, @target)
end
- loaded if target
+ loaded
target
end
@@ -415,7 +399,7 @@ module ActiveRecord
end
def reset_target!
- @target = Array.new
+ @target = []
end
def reset_scopes_cache!
@@ -450,6 +434,27 @@ module ActiveRecord
end
private
+ def merge_target_lists(loaded, existing)
+ return loaded if existing.empty?
+ return existing if loaded.empty?
+
+ loaded.map do |f|
+ i = existing.index(f)
+ if i
+ existing.delete_at(i).tap do |t|
+ keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
+ # FIXME: this call to attributes causes many NoMethodErrors
+ attributes = f.attributes
+ (attributes.keys - keys).each do |k|
+ t.send("#{k}=", attributes[k])
+ end
+ end
+ else
+ f
+ end
+ end + existing
+ end
+
# Do the relevant stuff to insert the given record into the association collection. The
# force param specifies whether or not an exception should be raised on failure. The
# validate param specifies whether validation should be performed (if force is false).
@@ -513,7 +518,7 @@ module ActiveRecord
def fetch_first_or_last_using_find?(args)
(args.first.kind_of?(Hash) && !args.first.empty?) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
- @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
+ @target.any? { |record| record.new_record? || record.changed? } || args.first.kind_of?(Integer))
end
def include_in_memory?(record)
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index f2de788522..addc64cb42 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -7,14 +7,15 @@ module ActiveRecord
# This is the root class of all association proxies ('+ Foo' signifies an included module Foo):
#
# AssociationProxy
- # BelongsToAssociation
- # BelongsToPolymorphicAssociation
+ # SingularAssociaton
+ # HasOneAssociation
+ # HasOneThroughAssociation + ThroughAssociation
+ # BelongsToAssociation
+ # BelongsToPolymorphicAssociation
# AssociationCollection
# HasAndBelongsToManyAssociation
# HasManyAssociation
# HasManyThroughAssociation + ThroughAssociation
- # HasOneAssociation
- # HasOneThroughAssociation + ThroughAssociation
#
# Association proxies in Active Record are middlemen between the object that
# holds the association, known as the <tt>@owner</tt>, and the actual associated
@@ -51,7 +52,7 @@ module ActiveRecord
# instantiation of the actual post records.
class AssociationProxy #:nodoc:
alias_method :proxy_extend, :extend
- delegate :to_param, :to => :proxy_target
+
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
def initialize(owner, reflection)
@@ -63,6 +64,10 @@ module ActiveRecord
construct_scope
end
+ def to_param
+ proxy_target.to_param
+ end
+
# Returns the owner of the proxy.
def proxy_owner
@owner
@@ -185,9 +190,8 @@ module ActiveRecord
scope = target_klass.unscoped
scope = scope.create_with(creation_attributes)
scope = scope.apply_finder_options(@reflection.options.slice(:conditions, :readonly, :include))
- scope = scope.where(construct_owner_conditions)
scope = scope.select(select_value) if select_value = self.select_value
- scope
+ scope.where(construct_owner_conditions)
end
def select_value
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 391471849c..e80b945dda 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -1,28 +1,17 @@
module ActiveRecord
# = Active Record Belongs To Associations
module Associations
- class BelongsToAssociation < AssociationProxy #:nodoc:
- def create(attributes = {})
- replace(@reflection.create_association(attributes))
- end
-
- def build(attributes = {})
- replace(@reflection.build_association(attributes))
- end
-
+ class BelongsToAssociation < SingularAssociation #:nodoc:
def replace(record)
- record = record.target if AssociationProxy === record
- raise_on_type_mismatch(record) unless record.nil?
+ record = check_record(record)
update_counters(record)
replace_keys(record)
set_inverse_instance(record)
- @target = record
@updated = true if record
- loaded
- record
+ self.target = record
end
def updated?
@@ -54,12 +43,8 @@ module ActiveRecord
@owner[@reflection.foreign_key] = record && record[@reflection.association_primary_key]
end
- def find_target
- scoped.first.tap { |record| set_inverse_instance(record) }
- end
-
def foreign_key_present?
- !@owner[@reflection.foreign_key].nil?
+ @owner[@reflection.foreign_key]
end
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index bc7894173d..b28554dce1 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -10,18 +10,6 @@ module ActiveRecord
super
end
- def columns
- @reflection.columns(@join_table_name, "#{@join_table_name} Columns")
- end
-
- def reset_column_information
- @reflection.reset_column_information
- end
-
- def has_primary_key?
- @has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@join_table_name)
- end
-
protected
def count_records
@@ -36,26 +24,11 @@ module ActiveRecord
if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
- relation = join_table
- timestamps = record_timestamp_columns(record)
- timezone = record.send(:current_time_from_proper_timezone) if timestamps.any?
-
- attributes = columns.map do |column|
- name = column.name
- value = case name.to_s
- when @reflection.foreign_key.to_s
- @owner.id
- when @reflection.association_foreign_key.to_s
- record.id
- when *timestamps
- timezone
- else
- @owner.send(:quote_value, record[name], column) if record.has_attribute?(name)
- end
- [relation[name], value] unless value.nil?
- end
+ stmt = join_table.compile_insert(
+ join_table[@reflection.foreign_key] => @owner.id,
+ join_table[@reflection.association_foreign_key] => record.id
+ )
- stmt = relation.compile_insert Hash[attributes]
@owner.connection.insert stmt.to_sql
end
@@ -89,46 +62,17 @@ module ActiveRecord
end
def association_scope
- scope = super.joins(construct_joins)
- scope = scope.readonly if ambiguous_select?(@reflection.options[:select])
- scope
+ super.joins(construct_joins)
end
def select_value
- super || [@reflection.klass.arel_table[Arel.star], join_table[Arel.star]]
- end
-
- # Join tables with additional columns on top of the two foreign keys must be considered
- # ambiguous unless a select clause has been explicitly defined. Otherwise you can get
- # broken records back, if, for example, the join column also has an id column. This will
- # then overwrite the id column of the records coming back.
- def ambiguous_select?(select)
- extra_join_columns? && select.nil?
- end
-
- def extra_join_columns?
- columns.size > 2
+ super || @reflection.klass.arel_table[Arel.star]
end
private
- def record_timestamp_columns(record)
- if record.record_timestamps
- record.send(:all_timestamp_attributes).map { |x| x.to_s }
- else
- []
- end
- end
-
def invertible_for?(record)
false
end
-
- def find_by_sql(*args)
- options = args.extract_options!
- ambiguous = ambiguous_select?(@reflection.options[:select] || options[:select])
-
- scoped.readonly(ambiguous).find(*(args << options))
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index c29ab8dcec..6614cbbf18 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,22 +1,9 @@
module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
- class HasOneAssociation < AssociationProxy #:nodoc:
- def create(attributes = {})
- new_record(:create_association, attributes)
- end
-
- def create!(attributes = {})
- build(attributes).tap { |record| record.save! }
- end
-
- def build(attributes = {})
- new_record(:build_association, attributes)
- end
-
+ class HasOneAssociation < SingularAssociation #:nodoc:
def replace(record, save = true)
- record = record.target if AssociationProxy === record
- raise_on_type_mismatch(record) unless record.nil?
+ record = check_record(record)
load_target
@reflection.klass.transaction do
@@ -36,15 +23,10 @@ module ActiveRecord
end
end
- @target = record
- loaded
+ self.target = record
end
private
- def find_target
- scoped.first.tap { |record| set_inverse_instance(record) }
- end
-
def association_scope
super.order(@reflection.options[:order])
end
@@ -52,12 +34,11 @@ module ActiveRecord
alias creation_attributes construct_owner_attributes
# The reason that the save param for replace is false, if for create (not just build),
- # is because the setting of the foreign keys is actually handled by the scoping, and
- # so they are set straight away and do not need to be updated within replace.
- def new_record(method, attributes)
- record = scoped.scoping { @reflection.send(method, attributes) }
+ # is because the setting of the foreign keys is actually handled by the scoping when
+ # the record is instantiated, and so they are set straight away and do not need to be
+ # updated within replace.
+ def set_new_record(record)
replace(record, false)
- record
end
def remove_target!(method)
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 59a704b7bf..dcd74e7346 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -4,29 +4,28 @@ module ActiveRecord
class HasOneThroughAssociation < HasOneAssociation
include ThroughAssociation
- def replace(new_value)
- create_through_record(new_value)
- @target = new_value
- loaded
+ def replace(record)
+ create_through_record(record)
+ self.target = record
end
private
- def create_through_record(new_value)
- proxy = @owner.send(:association_proxy, @reflection.through_reflection.name)
- record = proxy.send(:load_target)
+ def create_through_record(record)
+ through_proxy = @owner.send(:association_proxy, @reflection.through_reflection.name)
+ through_record = through_proxy.send(:load_target)
- if record && !new_value
- record.destroy
- elsif new_value
- attributes = construct_join_attributes(new_value)
+ if through_record && !record
+ through_record.destroy
+ elsif record
+ attributes = construct_join_attributes(record)
- if record
- record.update_attributes(attributes)
+ if through_record
+ through_record.update_attributes(attributes)
elsif @owner.new_record?
- proxy.build(attributes)
+ through_proxy.build(attributes)
else
- proxy.create(attributes)
+ through_proxy.create(attributes)
end
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
new file mode 100644
index 0000000000..b6f49c6f36
--- /dev/null
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -0,0 +1,41 @@
+module ActiveRecord
+ module Associations
+ class SingularAssociation < AssociationProxy #:nodoc:
+ def create(attributes = {})
+ record = scoped.scoping { @reflection.create_association(attributes) }
+ set_new_record(record)
+ record
+ end
+
+ def create!(attributes = {})
+ build(attributes).tap { |record| record.save! }
+ end
+
+ def build(attributes = {})
+ record = scoped.scoping { @reflection.build_association(attributes) }
+ set_new_record(record)
+ record
+ end
+
+ private
+ def find_target
+ scoped.first.tap { |record| set_inverse_instance(record) }
+ end
+
+ # Implemented by subclasses
+ def replace(record)
+ raise NotImplementedError
+ end
+
+ def set_new_record(record)
+ replace(record)
+ end
+
+ def check_record(record)
+ record = record.target if AssociationProxy === record
+ raise_on_type_mismatch(record) if record
+ record
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 660fa9a564..ff088200a1 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -60,8 +60,8 @@ module ActiveRecord
# Define an attribute reader method. Cope with nil column.
def define_read_method(symbol, attr_name, column)
- cast_code = column.type_cast_code('v') if column
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
+ cast_code = column.type_cast_code('v')
+ access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
unless attr_name.to_s == self.primary_key.to_s
access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index dde52269d4..00324b14f2 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1,3 +1,8 @@
+begin
+ require 'psych'
+rescue LoadError
+end
+
require 'yaml'
require 'set'
require 'active_support/benchmarkable'
@@ -829,7 +834,7 @@ module ActiveRecord #:nodoc:
def arel_engine
@arel_engine ||= begin
if self == ActiveRecord::Base
- Arel::Table.engine
+ ActiveRecord::Base
else
connection_handler.connection_pools[name] ? self : superclass.arel_engine
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 60ccf9edf3..bc8a6e9cd6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -87,8 +87,8 @@ module ActiveRecord
def type_cast_code(var_name)
case type
- when :string then nil
- when :text then nil
+ when :string then var_name
+ when :text then var_name
when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
when :float then "#{var_name}.to_f"
when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
@@ -98,7 +98,7 @@ module ActiveRecord
when :date then "#{self.class.name}.string_to_date(#{var_name})"
when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
- else nil
+ else var_name
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index b6f0511b9a..216c691833 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -1,4 +1,10 @@
require 'erb'
+
+begin
+ require 'psych'
+rescue LoadError
+end
+
require 'yaml'
require 'csv'
require 'zlib'
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index db5f1af5ca..69a7642ec5 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -63,6 +63,13 @@ module ActiveRecord
alias :& :merge
+ # Removes from the query the condition(s) specified in +skips+.
+ #
+ # Example:
+ #
+ # Post.order('id asc').except(:order) # discards the order condition
+ # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
+ #
def except(*skips)
result = self.class.new(@klass, table)
@@ -77,6 +84,13 @@ module ActiveRecord
result
end
+ # Removes any condition from the query other than the one(s) specified in +onlies+.
+ #
+ # Example:
+ #
+ # Post.order('id asc').only(:where) # discards the order condition
+ # Post.order('id asc').only(:where, :order) # uses the specified order
+ #
def only(*onlies)
result = self.class.new(@klass, table)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 868f761a33..48d2f7c9a9 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -325,16 +325,14 @@ module ActiveRecord
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
if @_start_transaction_state[:level] < 1
restore_state = remove_instance_variable(:@_start_transaction_state)
- if restore_state
- @attributes = @attributes.dup if @attributes.frozen?
- @new_record = restore_state[:new_record]
- @destroyed = restore_state[:destroyed]
- if restore_state.has_key?(:id)
- self.id = restore_state[:id]
- else
- @attributes.delete(self.class.primary_key)
- @attributes_cache.delete(self.class.primary_key)
- end
+ @attributes = @attributes.dup if @attributes.frozen?
+ @new_record = restore_state[:new_record]
+ @destroyed = restore_state[:destroyed]
+ if restore_state.has_key?(:id)
+ self.id = restore_state[:id]
+ else
+ @attributes.delete(self.class.primary_key)
+ @attributes_cache.delete(self.class.primary_key)
end
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index f367315b22..26c1a9db93 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -37,7 +37,7 @@ module ActiveRecord
end
end
- # The validation process on save can be skipped by passing false. The regular Base#save method is
+ # The validation process on save can be skipped by passing :validate => false. The regular Base#save method is
# replaced with this when the validations module is mixed in, which it is by default.
def save(options={})
perform_validations(options) ? super : false
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 853808eebf..e6a2b40403 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -85,11 +85,16 @@ module ActiveRecord
# can be named "davidhh".
#
# class Person < ActiveRecord::Base
- # validates_uniqueness_of :user_name, :scope => :account_id
+ # validates_uniqueness_of :user_name
# end
#
- # It can also validate whether the value of the specified attributes are unique based on multiple
- # scope parameters. For example, making sure that a teacher can only be on the schedule once
+ # It can also validate whether the value of the specified attributes are unique based on a scope parameter:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_uniqueness_of :user_name, :scope => :account_id
+ # end
+ #
+ # Or even multiple scope parameters. For example, making sure that a teacher can only be on the schedule once
# per semester for a particular class.
#
# class TeacherSchedule < ActiveRecord::Base