aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorXavier Noria <fxn@hashref.com>2010-06-30 20:47:26 +0200
committerXavier Noria <fxn@hashref.com>2010-06-30 20:47:26 +0200
commitc63cf7bf0db708fe46a929cf57649ab5a92034af (patch)
tree8f0974852b51597652e6ae73da26f3eb80fe878b /activerecord
parent52c56f9f7ec46ee39f1a6319ff4017e2492683ed (diff)
parentb07e6fdaa0aa07016d1425ada5b7f966142d0212 (diff)
downloadrails-c63cf7bf0db708fe46a929cf57649ab5a92034af.tar.gz
rails-c63cf7bf0db708fe46a929cf57649ab5a92034af.tar.bz2
rails-c63cf7bf0db708fe46a929cf57649ab5a92034af.zip
Merge remote branch 'rails/master'
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG8
-rw-r--r--activerecord/Rakefile4
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb11
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb6
-rw-r--r--activerecord/lib/active_record/base.rb75
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb1
-rw-r--r--activerecord/lib/active_record/named_scope.rb33
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake44
-rw-r--r--activerecord/lib/active_record/relation.rb45
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb14
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb (renamed from activerecord/test/cases/adapters/sqlite/copy_table_test.rb)0
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb (renamed from activerecord/test/cases/adapters/sqlite/sqlite3_adapter_test.rb)0
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb62
-rw-r--r--activerecord/test/cases/base_test.rb19
-rw-r--r--activerecord/test/cases/inheritance_test.rb2
-rw-r--r--activerecord/test/cases/method_scoping_test.rb213
-rw-r--r--activerecord/test/cases/migration_test.rb17
-rw-r--r--activerecord/test/cases/named_scope_test.rb10
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb396
-rw-r--r--activerecord/test/cases/relations_test.rb2
-rw-r--r--activerecord/test/models/developer.rb8
-rw-r--r--activerecord/test/models/without_table.rb3
26 files changed, 681 insertions, 302 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 09f897eaac..a1a82fdff5 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,6 +1,12 @@
*Rails 3.0.0 [RC1] (unreleased)*
-* PostgreSQL: ensure the database time zone matches Ruby's time zone. #4895 [Aaron Patterson]
+* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope [José Valim]
+
+* New rake task, db:migrate:status, displays status of migrations #4947 [Kevin Skoglund]
+
+* select and order for ActiveRecord now always concatenate nested calls. Use reorder if you want the original order to be overwritten [Santiago Pastorino]
+
+* PostgreSQL: ensure the database time zone matches Ruby's time zone #4895 [Aaron Patterson]
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 22a17a62af..392b717e0a 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -62,7 +62,9 @@ end
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
puts [adapter, adapter_short, connection_path].inspect
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
- Dir["test/cases/**/*_test{,_#{adapter_short}}.rb"].all? do |file|
+ (Dir["test/cases/**/*_test.rb"].reject {
+ |x| x =~ /\/adapters\//
+ } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file|
system(ruby, "-Ilib:test:#{connection_path}", file)
end or raise "Failures"
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 186b531ffb..ddf4ce4058 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -409,6 +409,17 @@ module ActiveRecord
end
def method_missing(method, *args)
+ case method.to_s
+ when 'find_or_create'
+ return find(:first, :conditions => args.first) || create(args.first)
+ when /^find_or_create_by_(.*)$/
+ rest = $1
+ return send("find_by_#{rest}", *args) ||
+ method_missing("create_by_#{rest}", *args)
+ when /^create_by_(.*)$/
+ return create Hash[$1.split('_and_').zip(args)]
+ end
+
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
if block_given?
super { |*block_args| yield(*block_args) }
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 92c6b3e770..d74fb7c702 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -110,7 +110,11 @@ module ActiveRecord
create_scoping = {}
set_belongs_to_association_for(create_scoping)
{
- :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
+ :find => { :conditions => @finder_sql,
+ :readonly => false,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :include => @reflection.options[:include]},
:create => create_scoping
}
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index e7b52287a5..c8795e4496 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -15,6 +15,7 @@ require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/blank'
@@ -278,6 +279,18 @@ module ActiveRecord #:nodoc:
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false
+ class << self
+ def reset_subclasses #:nodoc:
+ ActiveSupport::Deprecation.warn 'ActiveRecord::Base.reset_subclasses no longer does anything in Rails 3. It will be removed in the final release; please update your apps and plugins.', caller
+ end
+
+ def subclasses
+ descendants
+ end
+
+ deprecate :subclasses => :descendants
+ end
+
##
# :singleton-method:
# Contains the database configuration - as is typically stored in config/database.yml -
@@ -385,7 +398,7 @@ module ActiveRecord #:nodoc:
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
+ delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -707,14 +720,6 @@ module ActiveRecord #:nodoc:
end
alias :sequence_name= :set_sequence_name
- # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
- def class_name(table_name = table_name) # :nodoc:
- # remove any prefix and/or suffix from the table name
- class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
- class_name = class_name.singularize if pluralize_table_names
- class_name
- end
-
# Indicates whether the table associated with this class exists
def table_exists?
connection.table_exists?(table_name)
@@ -788,7 +793,7 @@ module ActiveRecord #:nodoc:
def reset_column_information
undefine_attribute_methods
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
- @arel_engine = @unscoped = @arel_table = nil
+ @arel_engine = @relation = @arel_table = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -891,9 +896,9 @@ module ActiveRecord #:nodoc:
store_full_sti_class ? name : name.demodulize
end
- def unscoped
- @unscoped ||= Relation.new(self, arel_table)
- finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
+ def relation
+ @relation ||= Relation.new(self, arel_table)
+ finder_needs_type_condition? ? @relation.where(type_condition) : @relation
end
def arel_table
@@ -910,6 +915,31 @@ module ActiveRecord #:nodoc:
end
end
+ # Returns a scope for this class without taking into account the default_scope.
+ #
+ # class Post < ActiveRecord::Base
+ # default_scope :published => true
+ # end
+ #
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
+ #
+ # This method also accepts a block meaning that all queries inside the block will
+ # not use the default_scope:
+ #
+ # Post.unscoped {
+ # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
+ # }
+ #
+ def unscoped
+ block_given? ? relation.scoping { yield } : relation
+ end
+
+ def scoped_methods #:nodoc:
+ key = :"#{self}_scoped_methods"
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
+ end
+
private
# Finder methods must instantiate through this method to work with the
# single-table inheritance model that makes it possible to create
@@ -1144,6 +1174,20 @@ module ActiveRecord #:nodoc:
# Works like with_scope, but discards any nested properties.
def with_exclusive_scope(method_scoping = {}, &block)
+ if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
+ raise ArgumentError, <<-MSG
+New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
+
+ User.unscoped.where(:active => true)
+
+Or call unscoped with a block:
+
+ User.unscoped do
+ User.where(:active => true).all
+ end
+
+MSG
+ end
with_scope(method_scoping, :overwrite, &block)
end
@@ -1157,11 +1201,6 @@ module ActiveRecord #:nodoc:
self.default_scoping << construct_finder_arel(options, default_scoping.pop)
end
- def scoped_methods #:nodoc:
- key = :"#{self}_scoped_methods"
- Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
- end
-
def current_scoped_methods #:nodoc:
scoped_methods.last
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 0216a8f4ac..76b65bf219 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -365,7 +365,7 @@ module ActiveRecord
end
def remove_index!(table_name, index_name) #:nodoc:
- execute "DROP INDEX #{quote_column_name(index_name)} ON #{table_name}"
+ execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
end
# Rename an index.
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index b273c33e50..4c5e1ae218 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/module/aliasing'
module ActiveRecord
# Exception that can be raised to stop migrations from going backwards.
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index ec0a98c6df..c010dac64e 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -25,10 +25,9 @@ module ActiveRecord
#
# You can define a \scope that applies to all finders using
# ActiveRecord::Base.default_scope.
- def scoped(options = {}, &block)
+ def scoped(options = nil)
if options.present?
- relation = scoped.apply_finder_options(options)
- block_given? ? relation.extending(Module.new(&block)) : relation
+ scoped.apply_finder_options(options)
else
current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
end
@@ -88,18 +87,22 @@ module ActiveRecord
# end
def scope(name, scope_options = {}, &block)
name = name.to_sym
+ valid_scope_name?(name)
- if !scopes[name] && respond_to?(name, true)
- logger.warn "Creating scope :#{name}. " \
- "Overwriting existing method #{self.name}.#{name}."
- end
+ extension = Module.new(&block) if block_given?
scopes[name] = lambda do |*args|
options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
- relation = scoped
- relation = options.is_a?(Hash) ? relation.apply_finder_options(options) : scoped.merge(options) if options
- block_given? ? relation.extending(Module.new(&block)) : relation
+ relation = if options.is_a?(Hash)
+ scoped.apply_finder_options(options)
+ elsif options
+ scoped.merge(options)
+ else
+ scoped
+ end
+
+ extension ? relation.extending(extension) : relation
end
singleton_class.send :define_method, name, &scopes[name]
@@ -109,7 +112,15 @@ module ActiveRecord
ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
scope(*args, &block)
end
- end
+ protected
+
+ def valid_scope_name?(name)
+ if !scopes[name] && respond_to?(name, true)
+ logger.warn "Creating scope :#{name}. " \
+ "Overwriting existing method #{self.name}.#{name}."
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 9e28aa2a05..50166c4385 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -182,7 +182,7 @@ module ActiveRecord
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
- @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
+ @attributes.update(self.class.unscoped { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
@attributes_cache = {}
self
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 006e64b115..7882f05d07 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -57,7 +57,7 @@ namespace :db do
end
rescue
case config['adapter']
- when 'mysql'
+ when /mysql/
@charset = ENV['CHARSET'] || 'utf8'
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
@@ -171,6 +171,36 @@ namespace :db do
ActiveRecord::Migrator.run(:down, "db/migrate/", version)
Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
end
+
+ desc "Display status of migrations"
+ task :status => :environment do
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
+ ActiveRecord::Base.establish_connection(config)
+ unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ puts 'Schema migrations table does not exist yet.'
+ next # means "return" for rake task
+ end
+ db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
+ file_list = []
+ Dir.foreach(File.join(Rails.root, 'db', 'migrate')) do |file|
+ # only files matching "20091231235959_some_name.rb" pattern
+ if match_data = /(\d{14})_(.+)\.rb/.match(file)
+ status = db_list.delete(match_data[1]) ? 'up' : 'down'
+ file_list << [status, match_data[1], match_data[2]]
+ end
+ end
+ # output
+ puts "\ndatabase: #{config['database']}\n\n"
+ puts "#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration Name"
+ puts "-" * 50
+ file_list.each do |file|
+ puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}"
+ end
+ db_list.each do |version|
+ puts "#{'up'.center(8)} #{version.ljust(14)} *** NO FILE ***"
+ end
+ puts
+ end
end
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
@@ -194,7 +224,7 @@ namespace :db do
task :charset => :environment do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.charset
when 'postgresql'
@@ -212,7 +242,7 @@ namespace :db do
task :collation => :environment do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.collation
else
@@ -313,7 +343,7 @@ namespace :db do
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
case abcs[Rails.env]["adapter"]
- when "mysql", "oci", "oracle"
+ when /mysql/, "oci", "oracle"
ActiveRecord::Base.establish_connection(abcs[Rails.env])
File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
@@ -361,7 +391,7 @@ namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
- when "mysql"
+ when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table|
@@ -395,7 +425,7 @@ namespace :db do
task :purge => :environment do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
- when "mysql"
+ when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
when "postgresql"
@@ -451,7 +481,7 @@ task 'test:prepare' => 'db:test:prepare'
def drop_database(config)
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
when /^sqlite/
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index fd0660a138..bc708b573f 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -16,7 +16,7 @@ module ActiveRecord
attr_reader :table, :klass
attr_accessor :extensions
- def initialize(klass, table, &block)
+ def initialize(klass, table)
@klass, @table = klass, table
@implicit_readonly = nil
@@ -25,12 +25,10 @@ module ActiveRecord
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
@extensions = []
-
- apply_modules(Module.new(&block)) if block_given?
end
def new(*args, &block)
- with_create_scope { @klass.new(*args, &block) }
+ scoping { @klass.new(*args, &block) }
end
def initialize_copy(other)
@@ -40,11 +38,11 @@ module ActiveRecord
alias build new
def create(*args, &block)
- with_create_scope { @klass.create(*args, &block) }
+ scoping { @klass.create(*args, &block) }
end
def create!(*args, &block)
- with_create_scope { @klass.create!(*args, &block) }
+ scoping { @klass.create!(*args, &block) }
end
def respond_to?(method, include_private = false)
@@ -102,6 +100,25 @@ module ActiveRecord
end
end
+ # Scope all queries to the current scope.
+ #
+ # ==== Example
+ #
+ # Comment.where(:post_id => 1).scoping do
+ # Comment.first #=> SELECT * FROM comments WHERE post_id = 1
+ # end
+ #
+ # Please check unscoped if you want to remove all previous scopes (including
+ # the default_scope) during the execution of a block.
+ def scoping
+ @klass.scoped_methods << self
+ begin
+ yield
+ ensure
+ @klass.scoped_methods.pop
+ end
+ end
+
# Updates all records with details given if they match a set of conditions supplied, limits and order can
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks
@@ -305,7 +322,6 @@ module ActiveRecord
if where.is_a?(Arel::Predicates::Equality)
hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
end
-
hash
end
end
@@ -328,15 +344,6 @@ module ActiveRecord
to_a.inspect
end
- def extend(*args, &block)
- if block_given?
- apply_modules Module.new(&block)
- self
- else
- super
- end
- end
-
protected
def method_missing(method, *args, &block)
@@ -345,7 +352,7 @@ module ActiveRecord
elsif @klass.scopes[method]
merge(@klass.send(method, *args, &block))
elsif @klass.respond_to?(method)
- @klass.send(:with_scope, self) { @klass.send(method, *args, &block) }
+ scoping { @klass.send(method, *args, &block) }
elsif arel.respond_to?(method)
arel.send(method, *args, &block)
elsif match = DynamicFinderMatch.match(method)
@@ -364,10 +371,6 @@ module ActiveRecord
private
- def with_create_scope
- @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
- end
-
def references_eager_loaded_tables?
# always convert table names to downcase as in Oracle quoted table names are in uppercase
joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map(&:downcase).uniq
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index d853fd63d1..5cea2328e8 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -20,9 +20,7 @@ module ActiveRecord
table = Arel::Table.new(table_name, :engine => @engine)
end
- unless attribute = table[column]
- raise StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
- end
+ attribute = table[column] || Arel::Attribute.new(table, column)
case value
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 015ca8c24c..4692271266 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -15,12 +15,10 @@ module ActiveRecord
end
def eager_load(*args)
- args.reject! { |a| a.blank? }
clone.tap { |r| r.eager_load_values += args if args.present? }
end
def preload(*args)
- args.reject! { |a| a.blank? }
clone.tap { |r| r.preload_values += args if args.present? }
end
@@ -28,29 +26,24 @@ module ActiveRecord
if block_given?
to_a.select { |*block_args| yield(*block_args) }
else
- args.reject! { |a| a.blank? }
clone.tap { |r| r.select_values += args if args.present? }
end
end
def group(*args)
- args.reject! { |a| a.blank? }
clone.tap { |r| r.group_values += args if args.present? }
end
def order(*args)
- args.reject! { |a| a.blank? }
clone.tap { |r| r.order_values += args if args.present? }
end
def reorder(*args)
- args.reject! { |a| a.blank? }
clone.tap { |r| r.order_values = args if args.present? }
end
def joins(*args)
args.flatten!
- args.reject! { |a| a.blank? }
clone.tap { |r| r.joins_values += args if args.present? }
end
@@ -93,8 +86,9 @@ module ActiveRecord
clone.tap { |r| r.from_value = value }
end
- def extending(*modules)
- clone.tap { |r| r.send :apply_modules, *modules }
+ def extending(*modules, &block)
+ modules << Module.new(&block) if block_given?
+ clone.tap { |r| r.send(:apply_modules, *modules) }
end
def reverse_order
@@ -201,7 +195,7 @@ module ActiveRecord
stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
- non_association_joins = (joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
+ non_association_joins = (joins - association_joins - stashed_association_joins)
custom_joins = custom_join_sql(*non_association_joins)
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index bb1f138f5b..7712ad2569 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -6,7 +6,7 @@ module ActiveRecord
merged_relation = clone
return merged_relation unless r
- (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).reject {|m| [:joins, :where].include?(m)}.each do |method|
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method|
value = r.send(:"#{method}_values")
merged_relation.send(:"#{method}_values=", value) if value.present?
end
diff --git a/activerecord/test/cases/adapters/sqlite/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 575b4806c1..575b4806c1 100644
--- a/activerecord/test/cases/adapters/sqlite/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
diff --git a/activerecord/test/cases/adapters/sqlite/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 934cf72f72..934cf72f72 100644
--- a/activerecord/test/cases/adapters/sqlite/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 45c7498013..5e3ba778f3 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -21,6 +21,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.destroyed_client_ids.clear
end
+ def test_create_by
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.find :first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find_by_post_id post.id
+
+ reader = person.readers.create_by_post_id post.id
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_create_by_multi
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.find :first
+
+ assert_equal [], person.readers
+
+ reader = person.readers.create_by_post_id_and_skimmer post.id, false
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_find_or_create_by
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.find :first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find_by_post_id post.id
+
+ reader = person.readers.find_or_create_by_post_id post.id
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_find_or_create
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.find :first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find(:first, :conditions => {
+ :post_id => post.id
+ })
+
+ reader = person.readers.find_or_create :post_id => post.id
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.each {|f| }
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index f8b90d8877..ba7db838ca 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -798,25 +798,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_raise(NoMethodError) { t.title2 }
end
- def test_class_name
- assert_equal "Firm", ActiveRecord::Base.class_name("firms")
- assert_equal "Category", ActiveRecord::Base.class_name("categories")
- assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")
-
- ActiveRecord::Base.pluralize_table_names = false
- assert_equal "Firms", ActiveRecord::Base.class_name( "firms" )
- ActiveRecord::Base.pluralize_table_names = true
-
- ActiveRecord::Base.table_name_prefix = "test_"
- assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" )
- ActiveRecord::Base.table_name_suffix = "_tests"
- assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" )
- ActiveRecord::Base.table_name_prefix = ""
- assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" )
- ActiveRecord::Base.table_name_suffix = ""
- assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
- end
-
def test_null_fields
assert_nil Topic.find(1).parent_id
assert_nil Topic.create("title" => "Hey you").parent_id
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index c1c8f01e46..8c09fc4d59 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -173,7 +173,7 @@ class InheritanceTest < ActiveRecord::TestCase
def test_complex_inheritance
very_special_client = VerySpecialClient.create("name" => "veryspecial")
- assert_equal very_special_client, VerySpecialClient.find(:first, :conditions => "name = 'veryspecial'")
+ assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first
assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'")
assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'")
assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'")
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 6cd42ff936..4e8ce1dac1 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -1,3 +1,7 @@
+# This file can be removed once with_exclusive_scope and with_scope are removed.
+# All the tests were already ported to relation_scoping_test.rb when the new
+# relation scoping API was added.
+
require "cases/helper"
require 'models/post'
require 'models/author'
@@ -283,6 +287,12 @@ class NestedScopingTest < ActiveRecord::TestCase
end
end
+ def test_with_exclusive_scope_with_relation
+ assert_raise(ArgumentError) do
+ Developer.all_johns
+ end
+ end
+
def test_append_conditions
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
@@ -533,205 +543,4 @@ class NestedScopingTest < ActiveRecord::TestCase
assert_equal 1, scoped_authors.size
assert_equal authors(:david).attributes, scoped_authors.first.attributes
end
-end
-
-class HasManyScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a comment...', Comment.what_are_you
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
-
- def test_forwarding_to_scoped
- assert_equal 4, Comment.search_by_type('Comment').size
- assert_equal 2, @welcome.comments.search_by_type('Comment').size
- end
-
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Comment.find_all_by_type('Comment').size
- assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
- end
-
- def test_nested_scope
- Comment.send(:with_scope, :find => { :conditions => '1=1' }) do
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
- end
-end
-
-class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
- fixtures :posts, :categories, :categories_posts
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a category...', Category.what_are_you
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
-
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Category.find_all_by_type('SpecialCategory').size
- assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
- assert_equal 2, @welcome.categories.find_all_by_type('Category').size
- end
-
- def test_nested_scope
- Category.send(:with_scope, :find => { :conditions => '1=1' }) do
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
- end
-end
-
-class DefaultScopingTest < ActiveRecord::TestCase
- fixtures :developers, :posts
-
- def test_default_scope
- expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
- assert_equal expected, received
- end
-
- def test_default_scope_with_conditions_string
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
- assert_equal nil, DeveloperCalledDavid.create!.name
- end
-
- def test_default_scope_with_conditions_hash
- assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
- assert_equal 'Jamis', DeveloperCalledJamis.create!.name
- end
-
- def test_default_scoping_with_threads
- 2.times do
- Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
- end
- end
-
- def test_default_scoping_with_inheritance
- # Inherit a class having a default scope and define a new default scope
- klass = Class.new(DeveloperOrderedBySalary)
- klass.send :default_scope, :limit => 1
-
- # Scopes added on children should append to parent scope
- assert_equal 1, klass.scoped.limit_value
- assert_equal ['salary DESC'], klass.scoped.order_values
-
- # Parent should still have the original scope
- assert_nil DeveloperOrderedBySalary.scoped.limit_value
- assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
- end
-
- def test_default_scope_called_twice_merges_conditions
- Developer.destroy_all
- Developer.create!(:name => "David", :salary => 80000)
- Developer.create!(:name => "David", :salary => 100000)
- Developer.create!(:name => "Brian", :salary => 100000)
-
- klass = Class.new(Developer)
- klass.__send__ :default_scope, :conditions => { :name => "David" }
- klass.__send__ :default_scope, :conditions => { :salary => 100000 }
- assert_equal 1, klass.count
- assert_equal "David", klass.first.name
- assert_equal 100000, klass.first.salary
- end
- def test_method_scope
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
- assert_equal expected, received
- end
-
- def test_nested_scope
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
- DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
- end
- assert_equal expected, received
- end
-
- def test_named_scope_overwrites_default
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
- assert_equal expected, received
- end
-
- def test_nested_exclusive_scope
- expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
- DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
- end
- assert_equal expected, received
- end
-
- def test_overwriting_default_scope
- expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
- assert_equal expected, received
- end
-
- def test_default_scope_using_relation
- posts = PostWithComment.scoped
- assert_equal 2, posts.count
- assert_equal posts(:thinking), posts.first
- end
-
- def test_create_attribute_overwrites_default_scoping
- assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
- assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
- end
-
- def test_create_attribute_overwrites_default_values
- assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
- assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
- end
-end
-
-=begin
-# We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
-
-class BelongsToScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts
-
- def setup
- @greetings = Comment.find(1)
- end
-
- def test_forwarding_of_static_method
- assert_equal 'a post...', Post.what_are_you
- assert_equal 'a post...', @greetings.post.what_are_you
- end
-
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Post.find_all_by_type('Post').size
- assert_equal 1, @greetings.post.find_all_by_type('Post').size
- end
-
-end
-
-class HasOneScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts
-
- def setup
- @sti_comments = Post.find(4)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a comment...', Comment.what_are_you
- assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you
- end
-
- def test_forwarding_to_dynamic_finders
- assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size
- assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size
- assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size
- end
-
-end
-
-=end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 99a3a12a8b..4ce9bdb46d 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1621,6 +1621,23 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ class ReservedWordsMigrationTest < ActiveRecord::TestCase
+ def test_drop_index_from_table_named_values
+ connection = Person.connection
+ connection.create_table :values, :force => true do |t|
+ t.integer :value
+ end
+
+ assert_nothing_raised do
+ connection.add_index :values, :value
+ connection.remove_index :values, :column => :value
+ end
+
+ connection.drop_table :values rescue nil
+ end
+ end
+
+
class ChangeTableMigrationsTest < ActiveRecord::TestCase
def setup
@connection = Person.connection
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 33ffb041c1..dc85b395d3 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -54,8 +54,8 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_respond_to_respects_include_private_parameter
- assert !Topic.approved.respond_to?(:with_create_scope)
- assert Topic.approved.respond_to?(:with_create_scope, true)
+ assert !Topic.approved.respond_to?(:tables_in_string)
+ assert Topic.approved.respond_to?(:tables_in_string, true)
end
def test_subclasses_inherit_scopes
@@ -450,6 +450,12 @@ class NamedScopeTest < ActiveRecord::TestCase
assert before.object_id != post.comments.containing_the_letter_e.object_id, "AssociationCollection##{method} should reset the named scopes cache"
end
end
+
+ def test_named_scoped_are_lazy_loaded_if_table_still_does_not_exist
+ assert_nothing_raised do
+ require "models/without_table"
+ end
+ end
end
class DynamicScopeMatchTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
new file mode 100644
index 0000000000..41dcdbcd37
--- /dev/null
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -0,0 +1,396 @@
+require "cases/helper"
+require 'models/post'
+require 'models/author'
+require 'models/developer'
+require 'models/project'
+require 'models/comment'
+require 'models/category'
+
+class RelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
+
+ def test_scoped_find
+ Developer.where("name = 'David'").scoping do
+ assert_nothing_raised { Developer.find(1) }
+ end
+ end
+
+ def test_scoped_find_first
+ developer = Developer.find(10)
+ Developer.where("salary = 100000").scoping do
+ assert_equal developer, Developer.order("name").first
+ end
+ end
+
+ def test_scoped_find_last
+ highest_salary = Developer.order("salary DESC").first
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ end
+ end
+
+ def test_scoped_find_last_preserves_scope
+ lowest_salary = Developer.first :order => "salary ASC"
+ highest_salary = Developer.first :order => "salary DESC"
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ assert_equal lowest_salary, Developer.first
+ end
+ end
+
+ def test_scoped_find_combines_and_sanitizes_conditions
+ Developer.where("salary = 9000").scoping do
+ assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
+ end
+ end
+
+ def test_scoped_find_all
+ Developer.where("name = 'David'").scoping do
+ assert_equal [developers(:david)], Developer.all
+ end
+ end
+
+ def test_scoped_find_select
+ Developer.select("id, name").scoping do
+ developer = Developer.where("name = 'David'").first
+ assert_equal "David", developer.name
+ assert !developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scope_select_concatenates
+ Developer.select("id, name").scoping do
+ developer = Developer.select('id, salary').where("name = 'David'").first
+ assert_equal 80000, developer.salary
+ assert developer.has_attribute?(:id)
+ assert developer.has_attribute?(:name)
+ assert developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scoped_count
+ Developer.where("name = 'David'").scoping do
+ assert_equal 1, Developer.count
+ end
+
+ Developer.where('salary = 100000').scoping do
+ assert_equal 8, Developer.count
+ assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
+ end
+ end
+
+ def test_scoped_find_include
+ # with the include, will retrieve only developers for the given project
+ scoped_developers = Developer.includes(:projects).scoping do
+ Developer.where('projects.id = 2').all
+ end
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ end
+
+ def test_scoped_find_joins
+ scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do
+ Developer.where('developers_projects.project_id = 2').all
+ end
+
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ assert_equal developers(:david).attributes, scoped_developers.first.attributes
+ end
+
+ def test_scoped_create_with_where
+ new_comment = VerySpecialComment.where(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with
+ new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with_has_higher_priority
+ new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_ensure_that_method_scoping_is_correctly_restored
+ scoped_methods = Developer.send(:current_scoped_methods)
+
+ begin
+ Developer.where("name = 'Jamis'").scoping do
+ raise "an exception"
+ end
+ rescue
+ end
+
+ assert_equal scoped_methods, Developer.send(:current_scoped_methods)
+ end
+end
+
+class NestedRelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts
+
+ def test_merge_options
+ Developer.where('salary = 80000').scoping do
+ Developer.limit(10).scoping do
+ devs = Developer.scoped
+ assert_equal '(salary = 80000)', devs.arel.send(:where_clauses).join(' AND ')
+ assert_equal 10, devs.taken
+ end
+ end
+ end
+
+ def test_merge_inner_scope_has_priority
+ Developer.limit(5).scoping do
+ Developer.limit(10).scoping do
+ assert_equal 10, Developer.count
+ end
+ end
+ end
+
+ def test_replace_options
+ Developer.where(:name => 'David').scoping do
+ Developer.unscoped do
+ assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
+ end
+
+ assert_equal 'David', Developer.first[:name]
+ end
+ end
+
+ def test_three_level_nested_exclusive_scoped_find
+ Developer.where("name = 'Jamis'").scoping do
+ assert_equal 'Jamis', Developer.first.name
+
+ Developer.unscoped.where("name = 'David'") do
+ assert_equal 'David', Developer.first.name
+
+ Developer.unscoped.where("name = 'Maiha'") do
+ assert_equal nil, Developer.first
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'David', Developer.first.name
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'Jamis', Developer.first.name
+ end
+ end
+
+ def test_nested_scoped_create
+ comment = Comment.create_with(:post_id => 1).scoping do
+ Comment.create_with(:post_id => 2).scoping do
+ Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
+ end
+ end
+
+ assert_equal 2, comment.post_id
+ end
+
+ def test_nested_exclusive_scope_for_create
+ comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
+ Comment.unscoped.create_with(:post_id => 1).scoping do
+ assert Comment.new.body.blank?
+ Comment.create :body => "Hey guys"
+ end
+ end
+
+ assert_equal 1, comment.post_id
+ assert_equal 'Hey guys', comment.body
+ end
+end
+
+class HasManyScopingTest< ActiveRecord::TestCase
+ fixtures :comments, :posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a comment...', Comment.what_are_you
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ def test_forwarding_to_scoped
+ assert_equal 4, Comment.search_by_type('Comment').size
+ assert_equal 2, @welcome.comments.search_by_type('Comment').size
+ end
+
+ def test_forwarding_to_dynamic_finders
+ assert_equal 4, Comment.find_all_by_type('Comment').size
+ assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
+ end
+
+ def test_nested_scope_finder
+ Comment.where('1=0').scoping do
+ assert_equal 0, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ Comment.where('1=1').scoping do
+ assert_equal 2, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+ end
+end
+
+class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
+ fixtures :posts, :categories, :categories_posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a category...', Category.what_are_you
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ def test_forwarding_to_dynamic_finders
+ assert_equal 4, Category.find_all_by_type('SpecialCategory').size
+ assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
+ assert_equal 2, @welcome.categories.find_all_by_type('Category').size
+ end
+
+ def test_nested_scope_finder
+ Category.where('1=0').scoping do
+ assert_equal 0, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ Category.where('1=1').scoping do
+ assert_equal 2, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+ end
+end
+
+class DefaultScopingTest < ActiveRecord::TestCase
+ fixtures :developers, :posts
+
+ def test_default_scope
+ expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ assert_equal expected, received
+ end
+
+ def test_default_scope_is_unscoped_on_find
+ assert_equal 1, DeveloperCalledDavid.count
+ assert_equal 11, DeveloperCalledDavid.unscoped.count
+ end
+
+ def test_default_scope_is_unscoped_on_create
+ assert_nil DeveloperCalledJamis.unscoped.create!.name
+ end
+
+ def test_default_scope_with_conditions_string
+ assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
+ assert_equal nil, DeveloperCalledDavid.create!.name
+ end
+
+ def test_default_scope_with_conditions_hash
+ assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
+ assert_equal 'Jamis', DeveloperCalledJamis.create!.name
+ end
+
+ def test_default_scoping_with_threads
+ 2.times do
+ Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
+ end
+ end
+
+ def test_default_scoping_with_inheritance
+ # Inherit a class having a default scope and define a new default scope
+ klass = Class.new(DeveloperOrderedBySalary)
+ klass.send :default_scope, :limit => 1
+
+ # Scopes added on children should append to parent scope
+ assert_equal 1, klass.scoped.limit_value
+ assert_equal ['salary DESC'], klass.scoped.order_values
+
+ # Parent should still have the original scope
+ assert_nil DeveloperOrderedBySalary.scoped.limit_value
+ assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
+ end
+
+ def test_default_scope_called_twice_merges_conditions
+ Developer.destroy_all
+ Developer.create!(:name => "David", :salary => 80000)
+ Developer.create!(:name => "David", :salary => 100000)
+ Developer.create!(:name => "Brian", :salary => 100000)
+
+ klass = Class.new(Developer)
+ klass.__send__ :default_scope, :conditions => { :name => "David" }
+ klass.__send__ :default_scope, :conditions => { :salary => 100000 }
+ assert_equal 1, klass.count
+ assert_equal "David", klass.first.name
+ assert_equal 100000, klass.first.salary
+ end
+ def test_method_scope
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
+ assert_equal expected, received
+ end
+
+ def test_nested_scope
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
+ DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ end
+ assert_equal expected, received
+ end
+
+ def test_named_scope_overwrites_default
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_nested_exclusive_scope
+ expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
+ DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ end
+ assert_equal expected, received
+ end
+
+ def test_overwriting_default_scope
+ expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
+ assert_equal expected, received
+ end
+
+ def test_default_scope_using_relation
+ posts = PostWithComment.scoped
+ assert_equal 2, posts.count
+ assert_equal posts(:thinking), posts.first
+ end
+
+ def test_create_attribute_overwrites_default_scoping
+ assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
+ assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
+ end
+
+ def test_create_attribute_overwrites_default_values
+ assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
+ assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index d27a69ea1c..ffde8daa07 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -618,7 +618,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_anonymous_extension
- relation = Post.where(:author_id => 1).order('id ASC').extend do
+ relation = Post.where(:author_id => 1).order('id ASC').extending do
def author
'lifo'
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index e35de3b9b0..de68fd7f24 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -55,6 +55,12 @@ class Developer < ActiveRecord::Base
def log=(message)
audit_logs.build :message => message
end
+
+ def self.all_johns
+ self.with_exclusive_scope :find => where(:name => 'John') do
+ self.all
+ end
+ end
end
class AuditLog < ActiveRecord::Base
@@ -103,4 +109,4 @@ end
class PoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
default_scope :conditions => { :name => 'Jamis', :salary => 50000 }
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb
new file mode 100644
index 0000000000..87f80911e1
--- /dev/null
+++ b/activerecord/test/models/without_table.rb
@@ -0,0 +1,3 @@
+class WithoutTable < ActiveRecord::Base
+ default_scope where(:published => true)
+end \ No newline at end of file