diff options
Diffstat (limited to 'activerecord')
31 files changed, 445 insertions, 278 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 4846774deb..38bcf0c787 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,12 @@ *Edge* +* Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default [DHH] + +* Add Relation#except. [Pratik Naik] + + one_red_item = Item.where(:colour => 'red').limit(1) + all_items = one_red_item.except(:where, :limit) + * Add Relation#delete_all. [Pratik Naik] Item.where(:colour => 'red').delete_all 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 diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index cf763d730a..116c8fc509 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -974,9 +974,9 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_default_invalid_error_from_i18n - I18n.backend.store_translations(:en, :activerecord => { :errors => { :models => + I18n.backend.store_translations(:en, :errors => { :models => { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } } - }}) + }) @pirate.send(@association_name).build(:name => '') @@ -985,9 +985,7 @@ module AutosaveAssociationOnACollectionAssociationTests assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages assert @pirate.errors[@association_name].empty? ensure - I18n.backend.store_translations(:en, :activerecord => { :errors => { :models => - { @association_name.to_s.singularize.to_sym => nil } - }}) + I18n.backend = I18n::Backend::Simple.new end def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 730d9d8df7..47e7a4bb79 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -2138,8 +2138,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_type_name_with_module_should_handle_beginning + ActiveRecord::Base.store_full_sti_class = false assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person') assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person') + ensure + ActiveRecord::Base.store_full_sti_class = true end def test_to_param_should_return_string diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 5a084a611e..ff2322ac15 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -113,6 +113,26 @@ class ImmutableMethodDeveloper < ActiveRecord::Base end end +class OnCallbacksDeveloper < ActiveRecord::Base + set_table_name 'developers' + + before_validation { history << :before_validation } + before_validation(:on => :create){ history << :before_validation_on_create } + before_validation(:on => :update){ history << :before_validation_on_update } + + validate do + history << :validate + end + + after_validation { history << :after_validation } + after_validation(:on => :create){ history << :after_validation_on_create } + after_validation(:on => :update){ history << :after_validation_on_update } + + def history + @history ||= [] + end +end + class CallbackCancellationDeveloper < ActiveRecord::Base set_table_name 'developers' @@ -250,7 +270,18 @@ class CallbacksTest < ActiveRecord::TestCase ], david.history end - def test_save + def test_validate_on_create + david = OnCallbacksDeveloper.create('name' => 'David', 'salary' => 1000000) + assert_equal [ + :before_validation, + :before_validation_on_create, + :validate, + :after_validation, + :after_validation_on_create + ], david.history + end + + def test_update david = CallbackDeveloper.find(1) david.save assert_equal [ @@ -297,6 +328,18 @@ class CallbacksTest < ActiveRecord::TestCase ], david.history end + def test_validate_on_update + david = OnCallbacksDeveloper.find(1) + david.save + assert_equal [ + :before_validation, + :before_validation_on_update, + :validate, + :after_validation, + :after_validation_on_update + ], david.history + end + def test_destroy david = CallbackDeveloper.find(1) david.destroy diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 479970b2fa..fa76e2d57a 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -13,8 +13,6 @@ require 'test/unit' require 'stringio' require 'active_record' -require 'active_record/test_case' -require 'active_record/fixtures' require 'connection' begin diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 73e51fbd91..0672fb938b 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -241,6 +241,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase end def test_instantiation_doesnt_try_to_require_corresponding_file + ActiveRecord::Base.store_full_sti_class = false foo = Firm.find(:first).clone foo.ruby_type = foo.type = 'FirmOnTheFly' foo.save! @@ -259,5 +260,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase # And instantiate will find the existing constant rather than trying # to require firm_on_the_fly. assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) } + ensure + ActiveRecord::Base.store_full_sti_class = true end end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 4f559bcaa5..d781a229f4 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -12,6 +12,8 @@ class ModulesTest < ActiveRecord::TestCase [:Firm, :Client].each do |const| @undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const) end + + ActiveRecord::Base.store_full_sti_class = false end def teardown @@ -19,6 +21,8 @@ class ModulesTest < ActiveRecord::TestCase @undefined_consts.each do |constant, value| Object.send :const_set, constant, value unless value.nil? end + + ActiveRecord::Base.store_full_sti_class = true end def test_module_spanning_associations diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 7c3e0f2ca6..6155bfd50a 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/entrant' +require 'models/bird' # So we can test whether Course.connection survives a reload. require_dependency 'models/course' @@ -82,4 +83,9 @@ class MultipleDbTest < ActiveRecord::TestCase assert_equal "Ruby Development", Course.find(1).name assert_equal "Ruby Developer", Entrant.find(1).name end + + def test_arel_table_engines + assert_not_equal Entrant.active_relation_engine, Course.active_relation_engine + assert_equal Entrant.active_relation_engine, Bird.active_relation_engine + end end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 8891282915..7ca9c416cb 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -34,7 +34,10 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase end def test_should_add_a_proc_to_nested_attributes_options - [:parrots, :birds, :birds_with_reject_all_blank].each do |name| + assert_equal ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC, + Pirate.nested_attributes_options[:birds_with_reject_all_blank][:reject_if] + + [:parrots, :birds].each do |name| assert_instance_of Proc, Pirate.nested_attributes_options[name][:reject_if] end end @@ -79,12 +82,6 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase assert ship._destroy end - def test_underscore_delete_is_deprecated - ActiveSupport::Deprecation.expects(:warn) - ship = Ship.create!(:name => 'Nights Dirty Lightning') - ship._delete - end - def test_reject_if_method_without_arguments Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record? @@ -176,6 +173,12 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end + def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record + assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do + @pirate.ship_attributes = { :id => 1234567890 } + end + end + def test_should_take_a_hash_with_string_keys_and_update_the_associated_model @pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' } @@ -269,6 +272,8 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase + include AssertRaiseWithMessage + def setup @ship = Ship.new(:name => 'Nights Dirty Lightning') @pirate = @ship.build_pirate(:catchphrase => 'Aye') @@ -323,6 +328,12 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase assert_equal 'Arr', @ship.pirate.catchphrase end + def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record + assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do + @ship.pirate_attributes = { :id => 1234567890 } + end + end + def test_should_take_a_hash_with_string_keys_and_update_the_associated_model @ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' } @@ -384,10 +395,6 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase assert Ship.reflect_on_association(:pirate).options[:autosave] end - def test_should_accept_update_only_option - @ship.update_attribute(:update_only_pirate_attributes, { :id => @pirate.ship.id, :catchphrase => 'Arr' }) - end - def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true @pirate.delete assert_difference('Pirate.count', 1) do @@ -460,6 +467,12 @@ module NestedAttributesOnACollectionAssociationTests assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] end + def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record + assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do + @pirate.attributes = { association_getter => [{ :id => 1234567890 }] } + end + end + def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing @pirate.send(@association_name).destroy_all @pirate.reload.attributes = { @@ -565,7 +578,7 @@ module NestedAttributesOnACollectionAssociationTests assert Pirate.reflect_on_association(@association_name).options[:autosave] end - def test_validate_presence_of_parent__works_with_inverse_of + def test_validate_presence_of_parent_works_with_inverse_of Man.accepts_nested_attributes_for(:interests) assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of] assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of] @@ -582,7 +595,7 @@ module NestedAttributesOnACollectionAssociationTests end end - def test_validate_presence_of_parent__fails_without_inverse_of + def test_validate_presence_of_parent_fails_without_inverse_of Man.accepts_nested_attributes_for(:interests) Man.reflect_on_association(:interests).options.delete(:inverse_of) Interest.reflect_on_association(:man).options.delete(:inverse_of) diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index acd214eb5a..774ab7aa4c 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -4,10 +4,13 @@ require 'models/customer' require 'models/company' require 'models/company_in_module' require 'models/subscriber' +require 'models/ship' require 'models/pirate' require 'models/price_estimate' class ReflectionTest < ActiveRecord::TestCase + include ActiveRecord::Reflection + fixtures :topics, :customers, :companies, :subscribers, :price_estimates def setup @@ -68,22 +71,22 @@ class ReflectionTest < ActiveRecord::TestCase end def test_reflection_klass_for_nested_class_name - reflection = ActiveRecord::Reflection::MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil) + reflection = MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil) assert_nothing_raised do assert_equal MyApplication::Business::Company, reflection.klass end end def test_aggregation_reflection - reflection_for_address = ActiveRecord::Reflection::AggregateReflection.new( + reflection_for_address = AggregateReflection.new( :composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer ) - reflection_for_balance = ActiveRecord::Reflection::AggregateReflection.new( + reflection_for_balance = AggregateReflection.new( :composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer ) - reflection_for_gps_location = ActiveRecord::Reflection::AggregateReflection.new( + reflection_for_gps_location = AggregateReflection.new( :composed_of, :gps_location, { }, Customer ) @@ -108,7 +111,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_has_many_reflection - reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm) + reflection_for_clients = AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm) assert_equal reflection_for_clients, Firm.reflect_on_association(:clients) @@ -120,7 +123,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_has_one_reflection - reflection_for_account = ActiveRecord::Reflection::AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm) + reflection_for_account = AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm) assert_equal reflection_for_account, Firm.reflect_on_association(:account) assert_equal Account, Firm.reflect_on_association(:account).klass @@ -137,6 +140,8 @@ class ReflectionTest < ActiveRecord::TestCase end def test_association_reflection_in_modules + ActiveRecord::Base.store_full_sti_class = false + assert_reflection MyApplication::Business::Firm, :clients_of_firm, :klass => MyApplication::Business::Client, @@ -172,6 +177,8 @@ class ReflectionTest < ActiveRecord::TestCase :klass => MyApplication::Billing::Nested::Firm, :class_name => 'Nested::Firm', :table_name => 'companies' + ensure + ActiveRecord::Base.store_full_sti_class = true end def test_reflection_of_all_associations @@ -187,7 +194,44 @@ class ReflectionTest < ActiveRecord::TestCase end def test_has_many_through_reflection - assert_kind_of ActiveRecord::Reflection::ThroughReflection, Subscriber.reflect_on_association(:books) + assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books) + end + + def test_collection_association + assert Pirate.reflect_on_association(:birds).collection_association? + assert Pirate.reflect_on_association(:parrots).collection_association? + + assert !Pirate.reflect_on_association(:ship).collection_association? + assert !Ship.reflect_on_association(:pirate).collection_association? + end + + def test_default_association_validation + assert AssociationReflection.new(:has_many, :clients, {}, Firm).validate? + + assert !AssociationReflection.new(:has_one, :client, {}, Firm).validate? + assert !AssociationReflection.new(:belongs_to, :client, {}, Firm).validate? + assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, {}, Firm).validate? + end + + def test_always_validate_association_if_explicit + assert AssociationReflection.new(:has_one, :client, { :validate => true }, Firm).validate? + assert AssociationReflection.new(:belongs_to, :client, { :validate => true }, Firm).validate? + assert AssociationReflection.new(:has_many, :clients, { :validate => true }, Firm).validate? + assert AssociationReflection.new(:has_and_belongs_to_many, :clients, { :validate => true }, Firm).validate? + end + + def test_validate_association_if_autosave + assert AssociationReflection.new(:has_one, :client, { :autosave => true }, Firm).validate? + assert AssociationReflection.new(:belongs_to, :client, { :autosave => true }, Firm).validate? + assert AssociationReflection.new(:has_many, :clients, { :autosave => true }, Firm).validate? + assert AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true }, Firm).validate? + end + + def test_never_validate_association_if_explicit + assert !AssociationReflection.new(:has_one, :client, { :autosave => true, :validate => false }, Firm).validate? + assert !AssociationReflection.new(:belongs_to, :client, { :autosave => true, :validate => false }, Firm).validate? + assert !AssociationReflection.new(:has_many, :clients, { :autosave => true, :validate => false }, Firm).validate? + assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true, :validate => false }, Firm).validate? end private diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 18f6152cc0..195889f1df 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -407,6 +407,11 @@ class RelationTest < ActiveRecord::TestCase end end + def test_relation_merging_with_locks + devs = Developer.lock.where("salary >= 80000").order("id DESC") & Developer.limit(2) + assert devs.locked.present? + end + def test_relation_merging_with_preload [Post.scoped & Post.preload(:author), Post.preload(:author) & Post.scoped].each do |posts| assert_queries(2) { assert posts.first.author } @@ -540,4 +545,23 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'hen', hen.name end + def test_explicit_create_scope + hens = Bird.where(:name => 'hen') + assert_equal 'hen', hens.new.name + + hens = hens.create_with(:name => 'cock') + assert_equal 'cock', hens.new.name + end + + def test_except + relation = Post.where(:author_id => 1).order('id ASC').limit(1) + assert_equal [posts(:welcome)], relation.all + + author_posts = relation.except(:order, :limit) + assert_equal Post.where(:author_id => 1).all, author_posts.all + + all_posts = relation.except(:where, :order, :limit) + assert_equal Post.all, all_posts.all + end + end diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index 3f96d7973b..15730c2a87 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -6,15 +6,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase def setup Topic.reset_callbacks(:validate) @topic = Topic.new - I18n.backend.store_translations :'en', { - :activerecord => { - :errors => { - :messages => { - :taken => "has already been taken", - } - } - } - } + I18n.backend = I18n::Backend::Simple.new end # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index f017f24048..5dfbb1516f 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -4,13 +4,14 @@ require 'models/reply' class I18nValidationTest < ActiveRecord::TestCase repair_validations(Topic, Reply) + def setup Reply.validates_presence_of(:title) @topic = Topic.new - @old_load_path, @old_backend = I18n.load_path, I18n.backend + @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', :activerecord => {:errors => {:messages => {:custom => nil}}}) + I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}}) end def teardown @@ -30,75 +31,6 @@ class I18nValidationTest < ActiveRecord::TestCase end end - # ActiveRecord::Errors - def test_errors_generate_message_translates_custom_model_attribute_key - I18n.expects(:translate).with( - :topic, - { :count => 1, - :default => ['Topic'], - :scope => [:activerecord, :models] - } - ).returns('Topic') - - I18n.expects(:translate).with( - :"topic.title", - { :count => 1, - :default => ['Title'], - :scope => [:activerecord, :attributes] - } - ).returns('Title') - - I18n.expects(:translate).with( - :"models.topic.attributes.title.invalid", - :value => nil, - :scope => [:activerecord, :errors], - :default => [ - :"models.topic.invalid", - 'default from class def error 1', - :"messages.invalid"], - :attribute => "Title", - :model => "Topic" - ).returns('default from class def error 1') - - @topic.errors.generate_message :title, :invalid, :default => 'default from class def error 1' - end - - def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti - - I18n.expects(:translate).with( - :reply, - { :count => 1, - :default => [:topic, 'Reply'], - :scope => [:activerecord, :models] - } - ).returns('Reply') - - I18n.expects(:translate).with( - :"reply.title", - { :count => 1, - :default => [:'topic.title', 'Title'], - :scope => [:activerecord, :attributes] - } - ).returns('Title') - - I18n.expects(:translate).with( - :"models.reply.attributes.title.invalid", - :value => nil, - :scope => [:activerecord, :errors], - :default => [ - :"models.reply.invalid", - :"models.topic.attributes.title.invalid", - :"models.topic.invalid", - 'default from class def', - :"messages.invalid"], - :model => 'Reply', - :attribute => 'Title' - ).returns("default from class def") - - Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def' - - end - # validates_uniqueness_of w/ mocha def test_validates_uniqueness_of_generates_message @@ -115,6 +47,25 @@ class I18nValidationTest < ActiveRecord::TestCase @topic.valid? end + # validates_uniqueness_of w/o mocha + + def test_validates_associated_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:title => {:taken => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}} + + Topic.validates_uniqueness_of :title + unique_topic.valid? + assert_equal ['custom message'], unique_topic.errors[:replies] + end + + def test_validates_associated_finds_global_default_translation + I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}} + + Topic.validates_uniqueness_of :title + unique_topic.valid? + assert_equal ['global message'], unique_topic.errors[:replies] + end + # validates_associated w/ mocha def test_validates_associated_generates_message @@ -132,8 +83,8 @@ class I18nValidationTest < ActiveRecord::TestCase # validates_associated w/o mocha def test_validates_associated_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}} Topic.validates_associated :replies replied_topic.valid? @@ -141,7 +92,7 @@ class I18nValidationTest < ActiveRecord::TestCase end def test_validates_associated_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}} Topic.validates_associated :replies replied_topic.valid? |