aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-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
12 files changed, 165 insertions, 74 deletions
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