aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorXavier Noria <fxn@hashref.com>2010-06-28 00:12:15 +0200
committerXavier Noria <fxn@hashref.com>2010-06-28 00:12:15 +0200
commit4329f8133fee8e4f3e558787f67de59f0c4a4dd1 (patch)
tree346ef7340d8348e50d119ca749a16c1654c20a08 /activerecord/lib
parentc37f7d66e49ffe5ac2115cc30e5529fd1c2924a8 (diff)
parentebee77a28a7267d5f23a28ba23c1eb88a2d7d527 (diff)
downloadrails-4329f8133fee8e4f3e558787f67de59f0c4a4dd1.tar.gz
rails-4329f8133fee8e4f3e558787f67de59f0c4a4dd1.tar.bz2
rails-4329f8133fee8e4f3e558787f67de59f0c4a4dd1.zip
Merge remote branch 'rails/master'
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/aggregations.rb6
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb14
-rw-r--r--activerecord/lib/active_record/autosave_association.rb4
-rw-r--r--activerecord/lib/active_record/base.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb52
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb20
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb32
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb19
-rw-r--r--activerecord/lib/active_record/railtie.rb3
-rw-r--r--activerecord/lib/active_record/railties/log_subscriber.rb32
-rw-r--r--activerecord/lib/active_record/relation.rb11
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb169
-rw-r--r--activerecord/lib/active_record/session_store.rb8
-rw-r--r--activerecord/lib/active_record/validations/associated.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb14
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb2
25 files changed, 279 insertions, 195 deletions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 51ffc7542c..c45400d3d9 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -190,7 +190,7 @@ module ActiveRecord
# :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
# :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
#
- def composed_of(part_id, options = {}, &block)
+ def composed_of(part_id, options = {})
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
name = part_id.id2name
@@ -199,9 +199,7 @@ module ActiveRecord
mapping = [ mapping ] unless mapping.first.is_a?(Array)
allow_nil = options[:allow_nil] || false
constructor = options[:constructor] || :new
- converter = options[:converter] || block
-
- ActiveSupport::Deprecation.warn('The conversion block has been deprecated, use the :converter option instead.', caller) if block_given?
+ converter = options[:converter]
reader_method(name, class_name, mapping, allow_nil, constructor)
writer_method(name, class_name, mapping, allow_nil, converter)
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index f8d46bcb48..186b531ffb 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -26,10 +26,10 @@ module ActiveRecord
delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
- def select(select = nil, &block)
+ def select(select = nil)
if block_given?
load_target
- @target.select(&block)
+ @target.select.each { |e| yield e }
else
scoped.select(select)
end
@@ -123,7 +123,7 @@ module ActiveRecord
end
end
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
def <<(*records)
result = true
@@ -168,7 +168,7 @@ module ActiveRecord
reset_target!
reset_named_scopes_cache!
end
-
+
# Calculate sum using SQL, not Enumerable
def sum(*args)
if block_given?
@@ -241,7 +241,7 @@ module ActiveRecord
if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
destroy_all
- else
+ else
delete_all
end
@@ -520,8 +520,8 @@ module ActiveRecord
def callbacks_for(callback_name)
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
- end
-
+ end
+
def ensure_owner_is_not_new
if @owner.new_record?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index c378e19864..7517896235 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -147,12 +147,12 @@ module ActiveRecord
# add_autosave_association_callbacks(reflect_on_association(name))
# end
ASSOCIATION_TYPES.each do |type|
- module_eval %{
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
def #{type}(name, options = {})
super
add_autosave_association_callbacks(reflect_on_association(name))
end
- }
+ CODE
end
# Adds a validate and save callback for the association as specified by
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 3f81ca7555..e7b52287a5 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -20,6 +20,7 @@ require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/blank'
require 'arel'
require 'active_record/errors'
+require 'active_record/log_subscriber'
module ActiveRecord #:nodoc:
# = Active Record
@@ -916,8 +917,8 @@ module ActiveRecord #:nodoc:
def instantiate(record)
object = find_sti_class(record[inheritance_column]).allocate
- object.instance_variable_set(:'@attributes', record)
- object.instance_variable_set(:'@attributes_cache', {})
+ object.instance_variable_set(:@attributes, record)
+ object.instance_variable_set(:@attributes_cache, {})
object.instance_variable_set(:@new_record, false)
object.instance_variable_set(:@readonly, false)
object.instance_variable_set(:@destroyed, false)
@@ -1413,14 +1414,6 @@ module ActiveRecord #:nodoc:
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
# application specific and is therefore left to the application to implement according to its need.
def initialize_copy(other)
- # Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
- # deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
- # over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
- # For example in the test suite the topic model's after_initialize method sets the author_email_address to
- # test@test.com. I would have thought this would mean that all cloned models would have an author email address
- # of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
- # after_initialize callback has to be run *before* the copying of the attributes rather than afterwards in order
- # for all tests to pass. This makes no sense to me.
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
cloned_attributes.delete(self.class.primary_key)
@@ -1433,6 +1426,7 @@ module ActiveRecord #:nodoc:
end
clear_aggregation_cache
+ clear_association_cache
@attributes_cache = {}
@new_record = true
ensure_proper_type
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 979ed52f4a..c2d79a421d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -144,7 +144,9 @@ module ActiveRecord
@connections.each do |conn|
conn.disconnect! if conn.requires_reloading?
end
- @connections = []
+ @connections.delete_if do |conn|
+ conn.requires_reloading?
+ end
end
# Verify active connections and remove and disconnect connections
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7d58bc2adf..7691b6a788 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -582,6 +582,11 @@ module ActiveRecord
@base.add_column(@table_name, column_name, type, options)
end
+ # Checks to see if a column exists. See SchemaStatements#column_exists?
+ def column_exists?(column_name, type = nil, options = nil)
+ @base.column_exists?(@table_name, column_name, type, options)
+ end
+
# Adds a new index to the table. +column_name+ can be a single Symbol, or
# an Array of Symbols. See SchemaStatements#add_index
#
@@ -596,6 +601,11 @@ module ActiveRecord
@base.add_index(@table_name, column_name, options)
end
+ # Checks to see if an index exists. See SchemaStatements#index_exists?
+ def index_exists?(column_name, options = {})
+ @base.index_exists?(@table_name, column_name, options)
+ end
+
# Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
# ===== Example
# t.timestamps
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 d3499cea72..0216a8f4ac 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -24,10 +24,53 @@ module ActiveRecord
# Returns an array of indexes for the given table.
# def indexes(table_name, name = nil) end
+ # Checks to see if an index exists on a table for a given index definition
+ #
+ # === Examples
+ # # Check an index exists
+ # index_exists?(:suppliers, :company_id)
+ #
+ # # Check an index on multiple columns exists
+ # index_exists?(:suppliers, [:company_id, :company_type])
+ #
+ # # Check a unique index exists
+ # index_exists?(:suppliers, :company_id, :unique => true)
+ #
+ # # Check an index with a custom name exists
+ # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
+ def index_exists?(table_name, column_name, options = {})
+ column_names = Array.wrap(column_name)
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
+ if options[:unique]
+ indexes(table_name).any?{ |i| i.unique && i.name == index_name }
+ else
+ indexes(table_name).any?{ |i| i.name == index_name }
+ end
+ end
+
# Returns an array of Column objects for the table specified by +table_name+.
# See the concrete implementation for details on the expected parameter values.
def columns(table_name, name = nil) end
+ # Checks to see if a column exists in a given table.
+ #
+ # === Examples
+ # # Check a column exists
+ # column_exists?(:suppliers, :name)
+ #
+ # # Check a column exists of a particular type
+ # column_exists?(:suppliers, :name, :string)
+ #
+ # # Check a column exists with a specific definition
+ # column_exists?(:suppliers, :name, :string, :limit => 100)
+ def column_exists?(table_name, column_name, type = nil, options = {})
+ columns(table_name).any?{ |c| c.name == column_name.to_s &&
+ (!type || c.type == type) &&
+ (!options[:limit] || c.limit == options[:limit]) &&
+ (!options[:precision] || c.precision == options[:precision]) &&
+ (!options[:scale] || c.scale == options[:scale]) }
+ end
+
# Creates a new table with the name +table_name+. +table_name+ may either
# be a String or a Symbol.
#
@@ -205,6 +248,7 @@ module ActiveRecord
# remove_column(:suppliers, :qualification)
# remove_columns(:suppliers, :qualification, :experience)
def remove_column(table_name, *column_names)
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
column_names.flatten.each do |column_name|
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
end
@@ -292,7 +336,7 @@ module ActiveRecord
@logger.warn("Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters. Skipping.")
return
end
- if index_exists?(table_name, index_name, false)
+ if index_name_exists?(table_name, index_name, false)
@logger.warn("Index name '#{index_name}' on table '#{table_name}' already exists. Skipping.")
return
end
@@ -313,7 +357,7 @@ module ActiveRecord
# remove_index :accounts, :name => :by_branch_party
def remove_index(table_name, options = {})
index_name = index_name(table_name, options)
- unless index_exists?(table_name, index_name, true)
+ unless index_name_exists?(table_name, index_name, true)
@logger.warn("Index name '#{index_name}' on table '#{table_name}' does not exist. Skipping.")
return
end
@@ -350,11 +394,11 @@ module ActiveRecord
end
end
- # Verify the existence of an index.
+ # Verify the existence of an index with a given name.
#
# The default argument is returned if the underlying implementation does not define the indexes method,
# as there's no way to determine the correct answer in that case.
- def index_exists?(table_name, index_name, default)
+ def index_name_exists?(table_name, index_name, default)
return default unless respond_to?(:indexes)
indexes(table_name).detect { |i| i.name == index_name }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4ee9fee4a9..be8d1bd76b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -107,7 +107,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
# Override to turn off referential integrity while executing <tt>&block</tt>.
- def disable_referential_integrity(&block)
+ def disable_referential_integrity
yield
end
@@ -142,9 +142,10 @@ module ActiveRecord
# this should be overridden by concrete adapters
end
- # Returns true if its safe to reload the connection between requests for development mode.
+ # Returns true if its required to reload the connection between requests for development mode.
+ # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
def requires_reloading?
- true
+ false
end
# Checks whether the connection to the database is still active (i.e. not stale).
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 7c7bc5e292..aa3626a37e 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -219,7 +219,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
- def disable_referential_integrity(&block) #:nodoc:
+ def disable_referential_integrity #:nodoc:
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
begin
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e84242601b..2fe2ae7136 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -216,7 +216,10 @@ module ActiveRecord
super(connection, logger)
@connection_parameters, @config = connection_parameters, config
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
+ @local_tz = nil
connect
+ @local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
end
# Is this connection alive and ready for queries?
@@ -372,7 +375,7 @@ module ActiveRecord
return false
end
- def disable_referential_integrity(&block) #:nodoc:
+ def disable_referential_integrity #:nodoc:
if supports_disable_referential_integrity?() then
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
end
@@ -606,27 +609,22 @@ module ActiveRecord
SQL
- indexes = []
-
- indexes = result.map do |row|
+ result.map do |row|
index_name = row[0]
unique = row[1] == 't'
indkey = row[2].split(" ")
oid = row[3]
- columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = r[0]; attlist}
- SELECT a.attname, a.attnum
+ columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
+ SELECT a.attnum, a.attname
FROM pg_attribute a
WHERE a.attrelid = #{oid}
AND a.attnum IN (#{indkey.join(",")})
SQL
- column_names = indkey.map {|attnum| columns[attnum] }
- IndexDefinition.new(table_name, index_name, unique, column_names)
-
- end
-
- indexes
+ column_names = columns.values_at(*indkey).compact
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
+ end.compact
end
# Returns the list of all column definitions for a table.
@@ -929,9 +927,8 @@ module ActiveRecord
# TIMESTAMP WITH ZONE types in UTC.
if ActiveRecord::Base.default_timezone == :utc
execute("SET time zone 'UTC'")
- else
- offset = Time.local(2000).utc_offset / 3600
- execute("SET time zone '#{offset}'")
+ elsif @local_tz
+ execute("SET time zone '#{@local_tz}'")
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index f295af16f0..0d9a86a1ea 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -6,6 +6,10 @@ module ActiveRecord
def self.sqlite3_connection(config) # :nodoc:
parse_sqlite_config!(config)
+ unless 'sqlite3' == config[:adapter]
+ raise ArgumentError, 'adapter name should be "sqlite3"'
+ end
+
unless self.class.const_defined?(:SQLite3)
require_library_or_gem(config[:adapter])
end
@@ -24,13 +28,13 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
class SQLite3Adapter < SQLiteAdapter # :nodoc:
-
+
# Returns the current database encoding format as a string, eg: 'UTF-8'
def encoding
if @connection.respond_to?(:encoding)
- @connection.encoding[0]['encoding']
+ @connection.encoding.to_s
else
- encoding = @connection.send(:get_query_pragma, 'encoding')
+ encoding = @connection.execute('PRAGMA encoding')
encoding[0]['encoding']
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index deb62e3802..1927585c49 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -151,7 +151,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def execute(sql, name = nil) #:nodoc:
- catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
+ log(sql, name) { @connection.execute(sql) }
end
def update_sql(sql, name = nil) #:nodoc:
@@ -176,15 +176,15 @@ module ActiveRecord
end
def begin_db_transaction #:nodoc:
- catch_schema_changes { @connection.transaction }
+ @connection.transaction
end
def commit_db_transaction #:nodoc:
- catch_schema_changes { @connection.commit }
+ @connection.commit
end
def rollback_db_transaction #:nodoc:
- catch_schema_changes { @connection.rollback }
+ @connection.rollback
end
# SCHEMA STATEMENTS ========================================
@@ -246,6 +246,7 @@ module ActiveRecord
end
def remove_column(table_name, *column_names) #:nodoc:
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
column_names.flatten.each do |column_name|
alter_table(table_name) do |definition|
definition.columns.delete(definition[column_name])
@@ -390,17 +391,6 @@ module ActiveRecord
end
end
- def catch_schema_changes
- return yield
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message =~ /database schema has changed/
- reconnect!
- retry
- else
- raise
- end
- end
-
def sqlite_version
@sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
new file mode 100644
index 0000000000..71065f9908
--- /dev/null
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ def initialize
+ super
+ @odd_or_even = false
+ end
+
+ def sql(event)
+ name = '%s (%.1fms)' % [event.payload[:name], event.duration]
+ sql = event.payload[:sql].squeeze(' ')
+
+ if odd?
+ name = color(name, :cyan, true)
+ sql = color(sql, nil, true)
+ else
+ name = color(name, :magenta, true)
+ end
+
+ debug " #{name} #{sql}"
+ end
+
+ def odd?
+ @odd_or_even = !@odd_or_even
+ end
+
+ def logger
+ ActiveRecord::Base.logger
+ end
+ end
+end
+
+ActiveRecord::LogSubscriber.attach_to :active_record \ No newline at end of file
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 12a75f5d16..c0302136ea 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -296,7 +296,9 @@ module ActiveRecord
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
elsif attributes['id']
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+ self.send(association_name.to_s+'=', existing_record)
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
@@ -366,11 +368,16 @@ module ActiveRecord
unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*UNASSIGNABLE_KEYS))
end
+
+ elsif existing_records.count == 0 #Existing record but not yet associated
+ existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
+ association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
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
@@ -390,7 +397,7 @@ module ActiveRecord
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
- # Determines if a new record should be build by checking for
+ # Determines if a new record should be built by checking for
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
@@ -406,9 +413,5 @@ module ActiveRecord
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 36df878e1b..2808e199fe 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -26,9 +26,6 @@ module ActiveRecord
load "active_record/railties/databases.rake"
end
- require "active_record/railties/log_subscriber"
- log_subscriber :active_record, ActiveRecord::Railties::LogSubscriber.new
-
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
diff --git a/activerecord/lib/active_record/railties/log_subscriber.rb b/activerecord/lib/active_record/railties/log_subscriber.rb
deleted file mode 100644
index 31b98bb6ed..0000000000
--- a/activerecord/lib/active_record/railties/log_subscriber.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module ActiveRecord
- module Railties
- class LogSubscriber < Rails::LogSubscriber
- def initialize
- super
- @odd_or_even = false
- end
-
- def sql(event)
- name = '%s (%.1fms)' % [event.payload[:name], event.duration]
- sql = event.payload[:sql].squeeze(' ')
-
- if odd?
- name = color(name, :cyan, true)
- sql = color(sql, nil, true)
- else
- name = color(name, :magenta, true)
- end
-
- debug " #{name} #{sql}"
- end
-
- def odd?
- @odd_or_even = !@odd_or_even
- end
-
- def logger
- ActiveRecord::Base.logger
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 66970a5ea1..fd0660a138 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
- delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a
+ delegate :to_xml, :to_json, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
delegate :insert, :to => :arel
attr_reader :table, :klass
@@ -328,6 +328,15 @@ 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)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7a0c9dc612..f39951e16c 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -87,8 +87,8 @@ module ActiveRecord
# person.visits += 1
# person.save!
# end
- def find(*args, &block)
- return to_a.find(&block) if block_given?
+ def find(*args)
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
options = args.extract_options!
@@ -259,8 +259,8 @@ module ActiveRecord
record
end
- def find_with_ids(*ids, &block)
- return to_a.find(&block) if block_given?
+ def find_with_ids(*ids)
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index d0efa2189d..d853fd63d1 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -28,7 +28,7 @@ module ActiveRecord
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
values = value.to_a
attribute.in(values)
- when Range
+ when Range, Arel::Relation
attribute.in(value)
else
attribute.eq(value)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 50e94134f5..015ca8c24c 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,79 +5,98 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
- included do
- (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
- attr_accessor :"#{query_method}_values"
-
- next if [:where, :having, :select].include?(query_method)
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(*args, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.#{query_method}_values += value if value.present?
- new_relation
- end
- CEVAL
- end
+ attr_accessor :includes_values, :eager_load_values, :preload_values,
+ :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values,
+ :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def select(*args, &block)
- if block_given?
- to_a.select(&block)
- else
- new_relation = clone
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.select_values += value if value.present?
- new_relation
- end
- end
- CEVAL
-
- [:where, :having].each do |query_method|
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(*args, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- value = build_where(*args)
- new_relation.#{query_method}_values += Array.wrap(value) if value.present?
- new_relation
- end
- CEVAL
- end
+ def includes(*args)
+ args.reject! { |a| a.blank? }
+ clone.tap { |r| r.includes_values += args if args.present? }
+ end
- ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
- attr_accessor :"#{query_method}_value"
+ def eager_load(*args)
+ args.reject! { |a| a.blank? }
+ clone.tap { |r| r.eager_load_values += args if args.present? }
+ end
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(value = true, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- new_relation.#{query_method}_value = value
- new_relation
- end
- CEVAL
+ def preload(*args)
+ args.reject! { |a| a.blank? }
+ clone.tap { |r| r.preload_values += args if args.present? }
+ end
+
+ def select(*args)
+ 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 extending(*modules)
- new_relation = clone
- new_relation.send :apply_modules, *modules
- new_relation
+ 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
+
+ def where(*args)
+ value = build_where(*args)
+ clone.tap { |r| r.where_values += Array.wrap(value) if value.present? }
end
- def lock(locks = true, &block)
- relation = clone
- relation.send(:apply_modules, Module.new(&block)) if block_given?
+ def having(*args)
+ value = build_where(*args)
+ clone.tap { |r| r.having_values += Array.wrap(value) if value.present? }
+ end
+
+ def limit(value = true)
+ clone.tap { |r| r.limit_value = value }
+ end
+ def offset(value = true)
+ clone.tap { |r| r.offset_value = value }
+ end
+
+ def lock(locks = true)
case locks
when String, TrueClass, NilClass
- clone.tap {|new_relation| new_relation.lock_value = locks || true }
+ clone.tap { |r| r.lock_value = locks || true }
else
- clone.tap {|new_relation| new_relation.lock_value = false }
+ clone.tap { |r| r.lock_value = false }
end
end
+ def readonly(value = true)
+ clone.tap { |r| r.readonly_value = value }
+ end
+
+ def create_with(value = true)
+ clone.tap { |r| r.create_with_value = value }
+ end
+
+ def from(value = true)
+ clone.tap { |r| r.from_value = value }
+ end
+
+ def extending(*modules)
+ clone.tap { |r| r.send :apply_modules, *modules }
+ end
+
def reverse_order
order_clause = arel.send(:order_clauses).join(', ')
relation = except(:order)
@@ -130,27 +149,18 @@ module ActiveRecord
end
end
- arel = arel.having(*@having_values.uniq.select{|h| h.present?})
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
arel = arel.take(@limit_value) if @limit_value.present?
arel = arel.skip(@offset_value) if @offset_value.present?
- arel = arel.group(*@group_values.uniq.select{|g| g.present?})
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}.map(&:to_s))
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
- selects = @select_values.uniq
+ arel = build_select(arel, @select_values.uniq)
- if selects.present?
- selects.each do |s|
- @implicit_readonly = false
- arel = arel.project(s) if s.present?
- end
- else
- arel = arel.project(@klass.quoted_table_name + '.*')
- end
-
- arel = @from_value.present? ? arel.from(@from_value) : arel.from(@klass.quoted_table_name)
+ arel = arel.from(@from_value) if @from_value.present?
case @lock_value
when TrueClass
@@ -221,6 +231,21 @@ module ActiveRecord
relation.join(custom_joins)
end
+ def build_select(arel, selects)
+ if selects.present?
+ @implicit_readonly = false
+ # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array.
+ # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array
+ if selects.all? { |s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
+ arel.project(*selects)
+ else
+ arel.project(selects.last)
+ end
+ else
+ arel.project(@klass.quoted_table_name + '.*')
+ end
+ end
+
def apply_modules(modules)
values = Array.wrap(modules)
@extensions += values if values.present?
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index f712a2c94f..b88d550086 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -318,6 +318,14 @@ module ActiveRecord
sid
end
+ def destroy(env)
+ if sid = current_session_id(env)
+ Base.silence do
+ get_session_model(env, sid).destroy
+ end
+ end
+ end
+
def get_session_model(env, sid)
if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
env[SESSION_RECORD_KEY] = find_session(sid)
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index e41635134c..0b0f5682aa 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -3,7 +3,7 @@ module ActiveRecord
class AssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
- record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
+ record.errors.add(attribute, :invalid, options.merge(:value => value))
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 6283bdd0d6..1c9ecc7b1b 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -32,7 +32,7 @@ module ActiveRecord
end
if relation.exists?
- record.errors.add(attribute, :taken, :default => options[:message], :value => value)
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index d6ab3257a0..8ac21c1410 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -1,15 +1,17 @@
class <%= migration_class_name %> < ActiveRecord::Migration
- def self.up<% attributes.each do |attribute| %>
- <%- if migration_action -%>
+ def self.up
+<% attributes.each do |attribute| -%>
+ <%- if migration_action -%>
<%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %>
- <%- end -%>
<%- end -%>
+<%- end -%>
end
- def self.down<% attributes.reverse.each do |attribute| %>
- <%- if migration_action -%>
+ def self.down
+<% attributes.reverse.each do |attribute| -%>
+ <%- if migration_action -%>
<%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %>
- <%- end -%>
<%- end -%>
+<%- end -%>
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 539c2517ee..960c29c49c 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -22,7 +22,7 @@ module ActiveRecord
def create_module_file
return if class_path.empty?
- template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb")
+ template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
end
hook_for :test_framework