aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb4
-rwxr-xr-xactiverecord/lib/active_record/associations.rb9
-rw-r--r--activerecord/lib/active_record/autosave_association.rb44
-rwxr-xr-xactiverecord/lib/active_record/base.rb12
-rw-r--r--activerecord/lib/active_record/callbacks.rb22
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb5
-rw-r--r--activerecord/lib/active_record/locale/en.yml44
-rw-r--r--activerecord/lib/active_record/migration.rb6
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb53
-rw-r--r--activerecord/lib/active_record/railtie.rb13
-rw-r--r--activerecord/lib/active_record/railties/databases.rake7
-rw-r--r--activerecord/lib/active_record/reflection.rb25
-rw-r--r--activerecord/lib/active_record/relation.rb74
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb85
-rw-r--r--activerecord/lib/active_record/test_case.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb2
18 files changed, 248 insertions, 167 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index cf439b0dc0..d5b6d40514 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -55,6 +55,7 @@ module ActiveRecord
autoload :FinderMethods
autoload :CalculationMethods
autoload :PredicateBuilder
+ autoload :SpawnMethods
end
autoload :Base
@@ -133,6 +134,9 @@ module ActiveRecord
autoload :AbstractAdapter
end
end
+
+ autoload :TestCase
+ autoload :TestFixtures, 'active_record/fixtures'
end
Arel::Table.engine = Arel::Sql::Engine.new(ActiveRecord::Base)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d74b21b690..1320f5b624 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1465,7 +1465,7 @@ module ActiveRecord
after_destroy(method_name)
end
- def find_with_associations(options = {}, join_dependency)
+ def find_with_associations(options, join_dependency)
rows = select_all_rows(options, join_dependency)
join_dependency.instantiate(rows)
rescue ThrowResult
@@ -1770,7 +1770,7 @@ module ActiveRecord
end
def using_limitable_reflections?(reflections)
- reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
+ reflections.collect(&:collection_association?).length.zero?
end
def column_aliases(join_dependency)
@@ -1843,7 +1843,7 @@ module ActiveRecord
case associations
when Symbol, String
reflection = base.reflections[associations]
- if reflection && [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
+ if reflection && reflection.collection_association?
records.each { |record| record.send(reflection.name).target.uniq! }
end
when Array
@@ -1853,12 +1853,11 @@ module ActiveRecord
when Hash
associations.keys.each do |name|
reflection = base.reflections[name]
- is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
parent_records = []
records.each do |record|
if descendant = record.send(reflection.name)
- if is_collection
+ if reflection.collection_association?
parent_records.concat descendant.target.uniq
else
parent_records << descendant
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 98ab64537e..f01d0903cd 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -158,47 +158,39 @@ module ActiveRecord
#
# For performance reasons, we don't check whether to validate at runtime,
# but instead only define the method and callback when needed. However,
- # this can change, for instance, when using nested attributes. Since we
- # don't want the callbacks to get defined multiple times, there are
- # guards that check if the save or validation methods have already been
- # defined before actually defining them.
+ # this can change, for instance, when using nested attributes, which is
+ # called _after_ the association has been defined. Since we don't want
+ # the callbacks to get defined multiple times, there are guards that
+ # check if the save or validation methods have already been defined
+ # before actually defining them.
def add_autosave_association_callbacks(reflection)
- save_method = "autosave_associated_records_for_#{reflection.name}"
- validation_method = "validate_associated_records_for_#{reflection.name}"
- force_validation = (reflection.options[:validate] == true || reflection.options[:autosave] == true)
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
+ collection = reflection.collection_association?
- case reflection.macro
- when :has_many, :has_and_belongs_to_many
- unless method_defined?(save_method)
+ unless method_defined?(save_method)
+ if collection
before_save :before_save_collection_association
define_method(save_method) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create save_method
after_update save_method
- end
-
- if !method_defined?(validation_method) &&
- (force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false))
- define_method(validation_method) { validate_collection_association(reflection) }
- validate validation_method
- end
- else
- unless method_defined?(save_method)
- case reflection.macro
- when :has_one
+ else
+ if reflection.macro == :has_one
define_method(save_method) { save_has_one_association(reflection) }
after_save save_method
- when :belongs_to
+ else
define_method(save_method) { save_belongs_to_association(reflection) }
before_save save_method
end
end
+ end
- if !method_defined?(validation_method) && force_validation
- define_method(validation_method) { validate_single_association(reflection) }
- validate validation_method
- end
+ if reflection.validate? && !method_defined?(validation_method)
+ method = (collection ? :validate_collection_association : :validate_single_association)
+ define_method(validation_method) { send(method, reflection) }
+ validate validation_method
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 70776c7aa2..026bf55aaa 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -550,7 +550,7 @@ module ActiveRecord #:nodoc:
# Determine whether to store the full constant name including namespace when using STI
superclass_delegating_accessor :store_full_sti_class
- self.store_full_sti_class = false
+ self.store_full_sti_class = true
# Stores the default scope for the class
class_inheritable_accessor :default_scoping, :instance_writer => false
@@ -1510,11 +1510,17 @@ module ActiveRecord #:nodoc:
end
def active_relation_table(table_name_alias = nil)
- Arel::Table.new(table_name, :as => table_name_alias)
+ Arel::Table.new(table_name, :as => table_name_alias, :engine => active_relation_engine)
end
def active_relation_engine
- @active_relation_engine ||= Arel::Sql::Engine.new(self)
+ @active_relation_engine ||= begin
+ if self == ActiveRecord::Base
+ Arel::Table.engine
+ else
+ connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.active_relation_engine
+ end
+ end
end
private
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index e2a8f03c8f..aecde5848c 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -9,7 +9,6 @@ module ActiveRecord
# * (-) <tt>valid</tt>
# * (1) <tt>before_validation</tt>
# * (-) <tt>validate</tt>
- # * (-) <tt>validate_on_create</tt>
# * (2) <tt>after_validation</tt>
# * (3) <tt>before_save</tt>
# * (4) <tt>before_create</tt>
@@ -223,9 +222,10 @@ module ActiveRecord
extend ActiveModel::Callbacks
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
- define_model_callbacks :validation, :only => [:before, :after]
end
module ClassMethods
@@ -236,6 +236,24 @@ module ActiveRecord
send(meth.to_sym, meth.to_sym)
end
end
+
+ def before_validation(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array(options[:if])
+ options[:if] << "@_on_validate == :#{options[:on]}"
+ end
+ set_callback(:validation, :before, *args, &block)
+ end
+
+ def after_validation(*args, &block)
+ options = args.extract_options!
+ options[:prepend] = true
+ options[:if] = Array(options[:if])
+ options[:if] << "!halted && value != false"
+ options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
+ set_callback(:validation, :after, *(args << options), &block)
+ end
end
def create_or_update_with_callbacks #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d09aa3c4d2..5eedf448a4 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -201,7 +201,7 @@ module ActiveRecord
protected
def log(sql, name)
result = nil
- ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name) do
+ ActiveSupport::Notifications.instrument("active_record.sql", :sql => sql, :name => name) do
@runtime += Benchmark.ms { result = yield }
end
result
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 99b812b5fc..b528c14bcc 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -3,7 +3,6 @@ require 'yaml'
require 'csv'
require 'zlib'
require 'active_support/dependencies'
-require 'active_support/test_case'
require 'active_support/core_ext/logger'
if RUBY_VERSION < '1.9'
@@ -823,8 +822,8 @@ module ActiveRecord
superclass_delegating_accessor :pre_loaded_fixtures
self.fixture_table_names = []
- self.use_transactional_fixtures = false
- self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = true
+ self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
self.fixture_class_names = {}
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index e33d389f8c..4115cc8e17 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -1,33 +1,9 @@
en:
- activerecord:
- errors:
- # model.errors.full_messages format.
- format: "{{attribute}} {{message}}"
-
- # The values :model, :attribute and :value are always available for interpolation
- # The value :count is available when applicable. Can be used for pluralization.
- messages:
- inclusion: "is not included in the list"
- exclusion: "is reserved"
- invalid: "is invalid"
- confirmation: "doesn't match confirmation"
- accepted: "must be accepted"
- empty: "can't be empty"
- blank: "can't be blank"
- too_long: "is too long (maximum is {{count}} characters)"
- too_short: "is too short (minimum is {{count}} characters)"
- wrong_length: "is the wrong length (should be {{count}} characters)"
- taken: "has already been taken"
- not_a_number: "is not a number"
- greater_than: "must be greater than {{count}}"
- greater_than_or_equal_to: "must be greater than or equal to {{count}}"
- equal_to: "must be equal to {{count}}"
- less_than: "must be less than {{count}}"
- less_than_or_equal_to: "must be less than or equal to {{count}}"
- odd: "must be odd"
- even: "must be even"
- record_invalid: "Validation failed: {{errors}}"
- # Append your own errors here or at the model/attributes scope.
+ errors:
+ messages:
+ taken: "has already been taken"
+ record_invalid: "Validation failed: {{errors}}"
+ # Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation.
@@ -42,7 +18,14 @@ en:
# Will define custom blank validation message for User model and
# custom blank validation message for login attribute of User model.
#models:
-
+
+ # Attributes names common to most models
+ #attributes:
+ #created_at: "Created at"
+ #updated_at: "Updated at"
+
+ # ActiveRecord models configuration
+ #activerecord:
# Translate model names. Used in Model.human_name().
#models:
# For example,
@@ -55,4 +38,3 @@ en:
# user:
# login: "Handle"
# will translate User attribute "login" as "Handle"
-
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 09a558a636..0022e8cc37 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -339,6 +339,10 @@ module ActiveRecord
self.verbose = save
end
+ def connection
+ ActiveRecord::Base.connection
+ end
+
def method_missing(method, *arguments, &block)
arg_list = arguments.map(&:inspect) * ', '
@@ -346,7 +350,7 @@ module ActiveRecord
unless arguments.empty? || method == :execute
arguments[0] = Migrator.proper_table_name(arguments.first)
end
- Base.connection.send(method, *arguments, &block)
+ connection.send(method, *arguments, &block)
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index ff3a51d5c0..9038888d22 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -188,6 +188,8 @@ module ActiveRecord
# the parent model is saved. This happens inside the transaction initiated
# by the parents save method. See ActiveRecord::AutosaveAssociation.
module ClassMethods
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
+
# Defines an attributes writer for the specified association(s). If you
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
# will need to add the attribute writer to the allowed list.
@@ -229,23 +231,14 @@ module ActiveRecord
options = { :allow_destroy => false, :update_only => false }
options.update(attr_names.extract_options!)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
+ 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)
- type = case reflection.macro
- when :has_one, :belongs_to
- :one_to_one
- when :has_many, :has_and_belongs_to_many
- :collection
- end
-
reflection.options[:autosave] = true
add_autosave_association_callbacks(reflection)
- self.nested_attributes_options[association_name.to_sym] = options
-
- if options[:reject_if] == :all_blank
- self.nested_attributes_options[association_name.to_sym][:reject_if] = proc { |attributes| attributes.all? {|k,v| v.blank?} }
- end
+ nested_attributes_options[association_name.to_sym] = options
+ type = (reflection.collection_association? ? :collection : :one_to_one)
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
@@ -271,21 +264,11 @@ module ActiveRecord
marked_for_destruction?
end
- # Deal with deprecated _delete.
- #
- def _delete #:nodoc:
- ActiveSupport::Deprecation.warn "_delete is deprecated in nested attributes. Use _destroy instead."
- _destroy
- end
-
private
# Attribute hash keys that should not be assigned as normal attributes.
# These hash keys are nested attributes implementation details.
- #
- # TODO Remove _delete from UNASSIGNABLE_KEYS when deprecation warning are
- # removed.
- UNASSIGNABLE_KEYS = %w( id _destroy _delete )
+ UNASSIGNABLE_KEYS = %w( id _destroy )
# Assigns the given attributes to the association.
#
@@ -298,13 +281,17 @@ module ActiveRecord
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
check_existing_record = (options[:update_only] || !attributes['id'].blank?)
if check_existing_record && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
+
+ elsif attributes['id']
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
+
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
@@ -343,7 +330,7 @@ module ActiveRecord
# { :id => '2', :_destroy => true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
- options = self.nested_attributes_options[association_name]
+ options = 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})"
@@ -366,6 +353,8 @@ module ActiveRecord
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, options[:allow_destroy])
+ else
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
end
end
end
@@ -382,8 +371,7 @@ module ActiveRecord
# Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
- ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) ||
- ConnectionAdapters::Column.value_to_boolean(hash['_delete']) # TODO Remove after deprecation.
+ ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
# Determines if a new record should be build by checking for
@@ -394,14 +382,17 @@ module ActiveRecord
end
def call_reject_if(association_name, attributes)
- callback = self.nested_attributes_options[association_name][:reject_if]
-
- case callback
+ case callback = nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
when Proc
- callback.try(:call, attributes)
+ callback.call(attributes)
end
end
+
+ def raise_nested_attributes_record_not_found(association_name, record_id)
+ reflection = self.class.reflect_on_association(association_name)
+ raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 657ee738c0..55008271b7 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -62,10 +62,21 @@ module ActiveRecord
initializer "active_record.notifications" do
require 'active_support/notifications'
- ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload|
+ ActiveSupport::Notifications.subscribe("active_record.sql") do |name, before, after, instrumenter_id, payload|
ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000)
end
end
+ initializer "active_record.i18n_deprecation" do
+ require 'active_support/i18n'
+
+ begin
+ I18n.t(:"activerecord.errors", :raise => true)
+ warn "[DEPRECATION] \"activerecord.errors\" namespace is deprecated in I18n " <<
+ "yml files, please use just \"errors\" instead."
+ rescue Exception => e
+ # No message then.
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index a35a6c156b..03359221c3 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -46,7 +46,7 @@ namespace :db do
$stderr.puts "Couldn't create database for #{config.inspect}"
end
end
- return # Skip the else clause of begin/rescue
+ return # Skip the else clause of begin/rescue
else
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection
@@ -246,6 +246,7 @@ namespace :db do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :load => :environment do
require 'active_record/fixtures'
+
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
@@ -257,7 +258,7 @@ namespace :db do
desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :identify => :environment do
- require "active_record/fixtures"
+ require 'active_record/fixtures'
label, id = ENV["LABEL"], ENV["ID"]
raise "LABEL or ID required" if label.blank? && id.blank?
@@ -295,7 +296,7 @@ namespace :db do
if File.exists?(file)
load(file)
else
- abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
+ abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/boot.rb to limit the frameworks that will be loaded}
end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index b751c9ad68..d6fad5cf29 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -252,10 +252,33 @@ module ActiveRecord
end
end
+ # Returns whether or not this association reflection is for a collection
+ # association. Returns +true+ if the +macro+ is one of +has_many+ or
+ # +has_and_belongs_to_many+, +false+ otherwise.
+ def collection_association?
+ if @collection_association.nil?
+ @collection_association = [:has_many, :has_and_belongs_to_many].include?(macro)
+ end
+ @collection_association
+ end
+
+ # Returns whether or not the association should be validated as part of
+ # the parent's validation.
+ #
+ # Unless you explicitely disable validation with
+ # <tt>:validate => false</tt>, it will take place when:
+ #
+ # * you explicitely enable validation; <tt>:validate => true</tt>
+ # * you use autosave; <tt>:autosave => true</tt>
+ # * the association is a +has_many+ association
+ def validate?
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
- class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
+ class_name = class_name.singularize if collection_association?
class_name
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 114095b7ef..487b54f27d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,18 +1,18 @@
module ActiveRecord
class Relation
- include QueryMethods, FinderMethods, CalculationMethods
+ include QueryMethods, FinderMethods, CalculationMethods, SpawnMethods
delegate :length, :collect, :map, :each, :all?, :to => :to_a
attr_reader :relation, :klass
attr_writer :readonly, :table
- attr_accessor :preload_associations, :eager_load_associations, :include_associations
+ attr_accessor :preload_associations, :eager_load_associations, :includes_associations, :create_with_attributes
def initialize(klass, relation)
@klass, @relation = klass, relation
@preload_associations = []
@eager_load_associations = []
- @include_associations = []
+ @includes_associations = []
@loaded, @readonly = false
end
@@ -28,38 +28,6 @@ module ActiveRecord
with_create_scope { @klass.create!(*args, &block) }
end
- def merge(r)
- raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
-
- merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations)
- merged_relation.readonly = r.readonly
-
- [self.relation, r.relation].each do |arel|
- merged_relation = merged_relation.
- joins(arel.joins(arel)).
- group(arel.groupings).
- order(arel.send(:order_clauses).join(', ')).
- limit(arel.taken).
- offset(arel.skipped).
- select(arel.send(:select_clauses)).
- from(arel.sources)
- end
-
- merged_wheres = @relation.wheres
-
- r.wheres.each do |w|
- if w.is_a?(Arel::Predicates::Equality)
- merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
- end
-
- merged_wheres << w
- end
-
- merged_relation.where(*merged_wheres)
- end
-
- alias :& :merge
-
def respond_to?(method, include_private = false)
return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method)
@@ -83,13 +51,13 @@ module ActiveRecord
:select => @relation.send(:select_clauses).join(', '),
:joins => @relation.joins(relation),
:group => @relation.send(:group_clauses).join(', '),
- :order => @relation.send(:order_clauses).join(', '),
+ :order => order_clause,
:conditions => where_clause,
:limit => @relation.taken,
:offset => @relation.skipped,
:from => (@relation.send(:from_clauses) if @relation.send(:sources).present?)
},
- ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @include_associations, nil))
+ ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @includes_associations, nil))
rescue ThrowResult
[]
end
@@ -98,7 +66,7 @@ module ActiveRecord
end
preload = @preload_associations
- preload += @include_associations unless find_with_associations
+ preload += @includes_associations unless find_with_associations
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
@records.each { |record| record.readonly! } if @readonly
@@ -156,23 +124,13 @@ module ActiveRecord
end
def reset
- @first = @last = @create_scope = @to_sql = nil
+ @first = @last = @to_sql = @order_clause = @scope_for_create = nil
@records = []
self
end
- def spawn(relation = @relation)
- relation = Relation.new(@klass, relation)
- relation.readonly = @readonly
- relation.preload_associations = @preload_associations
- relation.eager_load_associations = @eager_load_associations
- relation.include_associations = @include_associations
- relation.table = table
- relation
- end
-
def table
- @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass))
+ @table ||= Arel::Table.new(@klass.table_name, :engine => @klass.active_relation_engine)
end
def primary_key
@@ -205,13 +163,15 @@ module ActiveRecord
end
def with_create_scope
- @klass.send(:with_scope, :create => create_scope) { yield }
+ @klass.send(:with_scope, :create => scope_for_create) { yield }
end
- def create_scope
- @create_scope ||= wheres.inject({}) do |hash, where|
- hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
- hash
+ def scope_for_create
+ @scope_for_create ||= begin
+ @create_with_attributes || wheres.inject({}) do |hash, where|
+ hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
+ hash
+ end
end
end
@@ -219,6 +179,10 @@ module ActiveRecord
@relation.send(:where_clauses).join(join_string)
end
+ def order_clause
+ @order_clause ||= @relation.send(:order_clauses).join(', ')
+ end
+
def references_eager_loaded_tables?
joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq
(tables_in_string(to_sql) - joined_tables).any?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index cf2cc7ba70..5d7bf0b7bc 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -6,7 +6,7 @@ module ActiveRecord
end
def includes(*associations)
- spawn.tap {|r| r.include_associations += Array.wrap(associations) }
+ spawn.tap {|r| r.includes_associations += Array.wrap(associations) }
end
def eager_load(*associations)
@@ -17,6 +17,10 @@ module ActiveRecord
spawn.tap {|r| r.readonly = status }
end
+ def create_with(attributes = {})
+ spawn.tap {|r| r.create_with_attributes = attributes }
+ end
+
def select(selects)
if selects.present?
relation = spawn(@relation.project(selects))
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
new file mode 100644
index 0000000000..4ecee8c634
--- /dev/null
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -0,0 +1,85 @@
+module ActiveRecord
+ module SpawnMethods
+ def spawn(relation = @relation)
+ relation = Relation.new(@klass, relation)
+ relation.readonly = @readonly
+ relation.preload_associations = @preload_associations
+ relation.eager_load_associations = @eager_load_associations
+ relation.includes_associations = @includes_associations
+ relation.create_with_attributes = @create_with_attributes
+ relation.table = table
+ relation
+ end
+
+ def merge(r)
+ raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
+
+ merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.includes_associations)
+ merged_relation.readonly = r.readonly
+
+ [self.relation, r.relation].each do |arel|
+ merged_relation = merged_relation.
+ joins(arel.joins(arel)).
+ group(arel.groupings).
+ limit(arel.taken).
+ offset(arel.skipped).
+ select(arel.send(:select_clauses)).
+ from(arel.sources).
+ having(arel.havings).
+ lock(arel.locked)
+ end
+
+ relation_order = r.send(:order_clause)
+ merged_order = relation_order.present? ? relation_order : order_clause
+ merged_relation = merged_relation.order(merged_order)
+
+ merged_relation.create_with_attributes = @create_with_attributes
+
+ if @create_with_attributes && r.create_with_attributes
+ merged_relation.create_with_attributes = @create_with_attributes.merge(r.create_with_attributes)
+ else
+ merged_relation.create_with_attributes = r.create_with_attributes || @create_with_attributes
+ end
+
+ merged_wheres = @relation.wheres
+
+ r.wheres.each do |w|
+ if w.is_a?(Arel::Predicates::Equality)
+ merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
+ end
+
+ merged_wheres << w
+ end
+
+ merged_relation.where(*merged_wheres)
+ end
+
+ alias :& :merge
+
+ def except(*skips)
+ result = Relation.new(@klass, table)
+ result.table = table
+
+ [:eager_load, :preload, :includes].each do |load_method|
+ result = result.send(load_method, send(:"#{load_method}_associations"))
+ end
+
+ result.readonly = self.readonly unless skips.include?(:readonly)
+ result.create_with_attributes = @create_with_attributes unless skips.include?(:create_with)
+
+ result = result.joins(@relation.joins(@relation)) unless skips.include?(:joins)
+ result = result.group(@relation.groupings) unless skips.include?(:group)
+ result = result.limit(@relation.taken) unless skips.include?(:limit)
+ result = result.offset(@relation.skipped) unless skips.include?(:offset)
+ result = result.select(@relation.send(:select_clauses)) unless skips.include?(:select)
+ result = result.from(@relation.sources) unless skips.include?(:from)
+ result = result.order(order_clause) unless skips.include?(:order)
+ result = result.where(*@relation.wheres) unless skips.include?(:where)
+ result = result.having(*@relation.havings) unless skips.include?(:having)
+ result = result.lock(@relation.locked) unless skips.include?(:lock)
+
+ result
+ end
+
+ end
+end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 2dfe2c09ea..0a77ad5fd7 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -1,5 +1,3 @@
-require "active_support/test_case"
-
module ActiveRecord
class TestCase < ActiveSupport::TestCase #:nodoc:
def assert_date_from_db(expected, actual, message = nil)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 12c1f23763..d5adcba3ba 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -11,7 +11,7 @@ module ActiveRecord
def initialize(record)
@record = record
errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
- super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
+ super(I18n.t('errors.messages.record_invalid', :errors => errors))
end
end