aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb5
-rw-r--r--activerecord/lib/active_record/aggregations.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb18
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb79
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb56
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb14
-rw-r--r--activerecord/lib/active_record/core.rb31
-rw-r--r--activerecord/lib/active_record/fixtures.rb161
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb1
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb55
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb3
-rw-r--r--activerecord/lib/active_record/relation/merger.rb3
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb74
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb13
-rw-r--r--activerecord/lib/active_record/schema.rb17
-rw-r--r--activerecord/lib/active_record/session_store.rb15
-rw-r--r--activerecord/lib/active_record/store.rb18
-rw-r--r--activerecord/lib/active_record/transactions.rb10
27 files changed, 383 insertions, 276 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index ed26b4899f..210820062b 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -22,7 +22,6 @@
#++
require 'active_support'
-require 'active_support/i18n'
require 'active_model'
require 'arel'
require 'active_record_deprecated_finders'
@@ -145,4 +144,6 @@ ActiveSupport.on_load(:active_record) do
Arel::Table.engine = self
end
-I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index a4db627535..c7a329d74d 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -71,7 +71,7 @@ module ActiveRecord
# Now it's possible to access attributes from the database through the value objects instead. If
# you choose to name the composition the same as the attribute's name, it will be the only way to
# access that attribute. That's the case with our +balance+ attribute. You interact with the value
- # objects just like you would any other attribute, though:
+ # objects just like you would with any other attribute:
#
# customer.balance = Money.new(20) # sets the Money value object and the attribute
# customer.balance # => Money value object
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index c30e8e08b8..68f8bbeb1c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -543,7 +543,7 @@ module ActiveRecord
# end
#
# @group = Group.first
- # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
+ # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
# @group.avatars # selects all avatars by going through the User join model.
#
# An important caveat with going through +has_one+ or +has_many+ associations on the
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 14aa557b6c..3af5ff3eab 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -16,12 +16,6 @@ module ActiveRecord
# If you need to work on all current children, new and existing records,
# +load_target+ and the +loaded+ flag are your friends.
class CollectionAssociation < Association #:nodoc:
- attr_reader :proxy
-
- def initialize(owner, reflection)
- super
- @proxy = CollectionProxy.new(self)
- end
# Implements the reader method, e.g. foo.items for Foo.has_many :items
def reader(force_reload = false)
@@ -31,7 +25,7 @@ module ActiveRecord
reload
end
- proxy
+ CollectionProxy.new(self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -248,8 +242,12 @@ module ActiveRecord
# This method is abstract in the sense that it relies on
# +count_records+, which is a method descendants have to provide.
def size
- if !find_target? || (loaded? && !options[:uniq])
- target.size
+ if !find_target? || loaded?
+ if options[:uniq]
+ target.uniq.size
+ else
+ target.size
+ end
elsif !loaded? && options[:group]
load_target.size
elsif !loaded? && !options[:uniq] && target.is_a?(Array)
@@ -474,6 +472,8 @@ module ActiveRecord
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
"new records could not be saved."
end
+
+ new_target
end
def concat_records(records)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 261a829281..cf4cc98f38 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -33,14 +33,7 @@ module ActiveRecord
#
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
- class CollectionProxy # :nodoc:
- alias :proxy_extend :extend
-
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
-
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
- :lock, :readonly, :having, :pluck, :to => :scoped
-
+ class CollectionProxy < Relation # :nodoc:
delegate :target, :load_target, :loaded?, :to => :@association
delegate :select, :find, :first, :last,
@@ -52,7 +45,8 @@ module ActiveRecord
def initialize(association)
@association = association
- Array(association.options[:extend]).each { |ext| proxy_extend(ext) }
+ super association.klass, association.klass.arel_table
+ merge! association.scoped
end
alias_method :new, :build
@@ -61,54 +55,28 @@ module ActiveRecord
@association
end
- def scoped(options = nil)
- association = @association
- scope = association.scoped
-
- scope.extending! do
- define_method(:proxy_association) { association }
- end
- scope.merge!(options) if options
- scope
+ # We don't want this object to be put on the scoping stack, because
+ # that could create an infinite loop where we call an @association
+ # method, which gets the current scope, which is this object, which
+ # delegates to @association, and so on.
+ def scoping
+ @association.scoped.scoping { yield }
end
- def respond_to?(name, include_private = false)
- super ||
- (load_target && target.respond_to?(name, include_private)) ||
- proxy_association.klass.respond_to?(name, include_private)
+ def spawn
+ scoped
end
- def method_missing(method, *args, &block)
- match = DynamicMatchers::Method.match(self, method)
- if match && match.is_a?(DynamicMatchers::Instantiator)
- scoped.send(method, *args) do |r|
- proxy_association.send :set_owner_attributes, r
- proxy_association.send :add_to_target, r
- yield(r) if block_given?
- end
-
- elsif target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
- if load_target
- if target.respond_to?(method)
- target.send(method, *args, &block)
- else
- begin
- super
- rescue NoMethodError => e
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
- end
- end
- end
+ def scoped(options = nil)
+ association = @association
- else
- scoped.readonly(nil).public_send(method, *args, &block)
+ super.extending! do
+ define_method(:proxy_association) { association }
end
end
- # Forwards <tt>===</tt> explicitly to the \target because the instance method
- # removal above doesn't catch it. Loads the \target if needed.
- def ===(other)
- other === load_target
+ def ==(other)
+ load_target == other
end
def to_ary
@@ -130,19 +98,6 @@ module ActiveRecord
proxy_association.reload
self
end
-
- # Define array public methods because we know it should be invoked over
- # the target, so we can have a performance improvement using those methods
- # in association collections
- Array.public_instance_methods.each do |m|
- unless method_defined?(m)
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{m}(*args, &block)
- target.public_send(:#{m}, *args, &block) if load_target
- end
- RUBY
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 3005bef092..d545e7799d 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -350,7 +350,7 @@ module ActiveRecord
end
records_to_destroy.each do |record|
- association.proxy.destroy(record)
+ association.destroy(record)
end
end
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 f0b6ae2b7d..df78ba6c5a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -23,7 +23,7 @@ module ActiveRecord
end
def sql_type
- base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
+ base.type_to_sql(type.to_sym, limit, precision, scale)
end
def to_sql
@@ -262,7 +262,7 @@ module ActiveRecord
# Adds index options to the indexes hash, keyed by column name
# This is primarily used to track indexes that need to be created after the table
- # === Examples
+ #
# index(:account_id, :name => 'index_projects_on_account_id')
def index(column_name, options = {})
indexes[column_name] = options
@@ -348,7 +348,7 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
- # ===== Example
+ #
# ====== Creating a simple column
# t.column(:name, :string)
def column(column_name, type, options = {})
@@ -363,7 +363,6 @@ module ActiveRecord
# Adds a new index to the table. +column_name+ can be a single Symbol, or
# an Array of Symbols. See SchemaStatements#add_index
#
- # ===== Examples
# ====== Creating a simple index
# t.index(:name)
# ====== Creating a unique index
@@ -380,7 +379,7 @@ module ActiveRecord
end
# Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
- # ===== Example
+ #
# t.timestamps
def timestamps
@base.add_timestamps(@table_name)
@@ -388,7 +387,7 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
- # ===== Examples
+ #
# t.change(:name, :string, :limit => 80)
# t.change(:description, :text)
def change(column_name, type, options = {})
@@ -396,7 +395,7 @@ module ActiveRecord
end
# Sets a new default value for a column. See SchemaStatements#change_column_default
- # ===== Examples
+ #
# t.change_default(:qualification, 'new')
# t.change_default(:authorized, 1)
def change_default(column_name, default)
@@ -404,7 +403,7 @@ module ActiveRecord
end
# Removes the column(s) from the table definition.
- # ===== Examples
+ #
# t.remove(:qualification)
# t.remove(:qualification, :experience)
def remove(*column_names)
@@ -413,7 +412,6 @@ module ActiveRecord
# Removes the given index from the table.
#
- # ===== Examples
# ====== Remove the index_table_name_on_column in the table_name table
# t.remove_index :column
# ====== Remove the index named index_table_name_on_branch_id in the table_name table
@@ -427,14 +425,14 @@ module ActiveRecord
end
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
- # ===== Example
+ #
# t.remove_timestamps
def remove_timestamps
@base.remove_timestamps(@table_name)
end
# Renames a column.
- # ===== Example
+ #
# t.rename(:description, :name)
def rename(column_name, new_column_name)
@base.rename_column(@table_name, column_name, new_column_name)
@@ -442,7 +440,7 @@ module ActiveRecord
# Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
- # ===== Examples
+ #
# t.references(:goat)
# t.references(:goat, :polymorphic => true)
# t.belongs_to(:goat)
@@ -460,7 +458,7 @@ module ActiveRecord
# Removes a reference. Optionally removes a +type+ column.
# <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
- # ===== Examples
+ #
# t.remove_references(:goat)
# t.remove_references(:goat, :polymorphic => true)
# t.remove_belongs_to(:goat)
@@ -475,7 +473,7 @@ module ActiveRecord
alias :remove_belongs_to :remove_references
# Adds a column or columns of a specified type
- # ===== Examples
+ #
# t.string(:goat)
# t.string(:goat, :sheep)
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
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 e7a4f061fd..62b0f51bb2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -21,7 +21,6 @@ module ActiveRecord
# Checks to see if the table +table_name+ exists on the database.
#
- # === Example
# table_exists?(:developers)
def table_exists?(table_name)
tables.include?(table_name.to_s)
@@ -32,7 +31,6 @@ module ActiveRecord
# 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)
#
@@ -126,7 +124,6 @@ module ActiveRecord
# Set to true to drop the table before creating it.
# Defaults to false.
#
- # ===== Examples
# ====== Add a backend specific option to the generated SQL (MySQL)
# create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
# generates:
@@ -193,7 +190,6 @@ module ActiveRecord
# Set to true to drop the table before creating it.
# Defaults to false.
#
- # ===== Examples
# ====== Add a backend specific option to the generated SQL (MySQL)
# create_join_table(:assemblies, :parts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
# generates:
@@ -215,7 +211,6 @@ module ActiveRecord
# A block for changing columns in +table+.
#
- # === Example
# # change_table() yields a Table instance
# change_table(:suppliers) do |t|
# t.column :name, :string, :limit => 60
@@ -229,7 +224,6 @@ module ActiveRecord
#
# Defaults to false.
#
- # ===== Examples
# ====== Add a column
# change_table(:suppliers) do |t|
# t.column :name, :string, :limit => 60
@@ -288,7 +282,7 @@ module ActiveRecord
end
# Renames a table.
- # ===== Example
+ #
# rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
raise NotImplementedError, "rename_table is not implemented"
@@ -308,7 +302,7 @@ module ActiveRecord
end
# Removes the column(s) from the table definition.
- # ===== Examples
+ #
# remove_column(:suppliers, :qualification)
# remove_columns(:suppliers, :qualification, :experience)
def remove_column(table_name, *column_names)
@@ -318,7 +312,7 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
- # ===== Examples
+ #
# change_column(:suppliers, :name, :string, :limit => 80)
# change_column(:accounts, :description, :text)
def change_column(table_name, column_name, type, options = {})
@@ -326,7 +320,7 @@ module ActiveRecord
end
# Sets a new default value for a column.
- # ===== Examples
+ #
# change_column_default(:suppliers, :qualification, 'new')
# change_column_default(:accounts, :authorized, 1)
# change_column_default(:users, :email, nil)
@@ -335,7 +329,7 @@ module ActiveRecord
end
# Renames a column.
- # ===== Example
+ #
# rename_column(:suppliers, :description, :name)
def rename_column(table_name, column_name, new_column_name)
raise NotImplementedError, "rename_column is not implemented"
@@ -347,8 +341,6 @@ module ActiveRecord
# The index will be named after the table and the column name(s), unless
# you pass <tt>:name</tt> as an option.
#
- # ===== Examples
- #
# ====== Creating a simple index
# add_index(:suppliers, :name)
# generates
@@ -537,7 +529,7 @@ module ActiveRecord
end
# Adds timestamps (created_at and updated_at) columns to the named table.
- # ===== Examples
+ #
# add_timestamps(:suppliers)
def add_timestamps(table_name)
add_column table_name, :created_at, :datetime
@@ -545,7 +537,7 @@ module ActiveRecord
end
# Removes the timestamp columns (created_at and updated_at) from the table definition.
- # ===== Examples
+ #
# remove_timestamps(:suppliers)
def remove_timestamps(table_name)
remove_column table_name, :updated_at
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index a848838a4e..9794c5663e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -251,7 +251,7 @@ module ActiveRecord
end
# MysqlAdapter has to free a result after using it, so we use this method to write
- # stuff in a abstract way without concerning ourselves about whether it needs to be
+ # stuff in an abstract way without concerning ourselves about whether it needs to be
# explicitly freed or not.
def execute_and_free(sql, name = nil) #:nodoc:
yield execute(sql, name)
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 1933ce2b46..01bd3ae26c 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -124,6 +124,7 @@ module ActiveRecord
when :binary then "#{klass}.binary_to_string(#{var_name})"
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
when :hstore then "#{klass}.string_to_hstore(#{var_name})"
+ when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
else var_name
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index c82afc232c..df3d5e4657 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -137,6 +137,14 @@ module ActiveRecord
end
end
+ class Cidr < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
+ end
+ end
+
class TypeMap
def initialize
@mapping = {}
@@ -212,11 +220,9 @@ module ActiveRecord
# FIXME: why are we keeping these types as strings?
alias_type 'tsvector', 'text'
alias_type 'interval', 'text'
- alias_type 'cidr', 'text'
- alias_type 'inet', 'text'
- alias_type 'macaddr', 'text'
alias_type 'bit', 'text'
alias_type 'varbit', 'text'
+ alias_type 'macaddr', 'text'
# FIXME: I don't think this is correct. We should probably be returning a parsed date,
# but the tests pass with a string returned.
@@ -237,6 +243,9 @@ module ActiveRecord
register_type 'polygon', OID::Identity.new
register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
+
+ register_type 'cidr', OID::Cidr.new
+ alias_type 'inet', 'cidr'
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 5ec1ffd704..14bc95abfe 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -8,6 +8,8 @@ require 'arel/visitors/bind_visitor'
gem 'pg', '~> 0.11'
require 'pg'
+require 'ipaddr'
+
module ActiveRecord
module ConnectionHandling
# Establishes a connection to the database that's used by all Active Record objects
@@ -79,6 +81,25 @@ module ActiveRecord
end
end
+ def string_to_cidr(string)
+ if string.nil?
+ nil
+ elsif String === string
+ IPAddr.new(string)
+ else
+ string
+ end
+
+ end
+
+ def cidr_to_string(object)
+ if IPAddr === object
+ "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
+ else
+ object
+ end
+ end
+
private
HstorePair = begin
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
@@ -197,6 +218,13 @@ module ActiveRecord
:decimal
when 'hstore'
:hstore
+ # Network address types
+ when 'inet'
+ :inet
+ when 'cidr'
+ :cidr
+ when 'macaddr'
+ :macaddr
# Character types
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
:string
@@ -211,9 +239,6 @@ module ActiveRecord
# Geometric types
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
:string
- # Network address types
- when /^(?:cidr|inet|macaddr)$/
- :string
# Bit strings
when /^bit(?: varying)?(?:\(\d+\))?$/
:string
@@ -282,6 +307,18 @@ module ActiveRecord
def hstore(name, options = {})
column(name, 'hstore', options)
end
+
+ def inet(name, options = {})
+ column(name, 'inet', options)
+ end
+
+ def cidr(name, options = {})
+ column(name, 'cidr', options)
+ end
+
+ def macaddr(name, options = {})
+ column(name, 'macaddr', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -301,7 +338,10 @@ module ActiveRecord
:boolean => { :name => "boolean" },
:xml => { :name => "xml" },
:tsvector => { :name => "tsvector" },
- :hstore => { :name => "hstore" }
+ :hstore => { :name => "hstore" },
+ :inet => { :name => "inet" },
+ :cidr => { :name => "cidr" },
+ :macaddr => { :name => "macaddr" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -510,6 +550,11 @@ module ActiveRecord
when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
else super
end
+ when IPAddr
+ case column.sql_type
+ when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
+ else super
+ end
when Float
if value.infinite? && column.type == :datetime
"'#{value.to_s.downcase}'"
@@ -549,6 +594,9 @@ module ActiveRecord
when Hash
return super unless 'hstore' == column.sql_type
PostgreSQLColumn.hstore_to_string(value)
+ when IPAddr
+ return super unless ['inet','cidr'].includes? column.sql_type
+ PostgreSQLColumn.cidr_to_string(value)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 44e407a561..d4ffa82b17 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -21,10 +21,6 @@ module ActiveRecord
config[:database] = File.expand_path(config[:database], Rails.root)
end
- unless 'sqlite3' == config[:adapter]
- raise ArgumentError, 'adapter name should be "sqlite3"'
- end
-
db = SQLite3::Database.new(
config[:database],
:results_as_hash => true
@@ -195,7 +191,7 @@ module ActiveRecord
:decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "datetime" },
- :time => { :name => "time" },
+ :time => { :name => "datetime" },
:date => { :name => "date" },
:binary => { :name => "blob" },
:boolean => { :name => "boolean" }
@@ -522,7 +518,11 @@ module ActiveRecord
end
def copy_table(from, to, options = {}) #:nodoc:
- options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
+ from_primary_key = primary_key(from)
+ options[:primary_key] = from_primary_key if from_primary_key != 'id'
+ unless options[:primary_key]
+ options[:id] = columns(from).detect{|c| c.name == 'id'}.present? && from_primary_key == 'id'
+ end
create_table(to, options) do |definition|
@definition = definition
columns(from).each do |column|
@@ -536,7 +536,7 @@ module ActiveRecord
:precision => column.precision, :scale => column.scale,
:null => column.null)
end
- @definition.primary_key(primary_key(from)) if primary_key(from)
+ @definition.primary_key(from_primary_key) if from_primary_key
yield @definition if block_given?
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index a869ed8c04..b2ed606e5f 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -77,7 +77,7 @@ module ActiveRecord
##
# :singleton-method:
- # Specifies wether or not has_many or has_one association option
+ # Specifies whether or not has_many or has_one association option
# :dependent => :restrict raises an exception. If set to true, the
# ActiveRecord::DeleteRestrictionError exception will be raised
# along with a DEPRECATION WARNING. If set to false, an error would
@@ -127,10 +127,16 @@ module ActiveRecord
object.is_a?(self)
end
+ # Returns an instance of <tt>Arel::Table</tt> loaded with the curent table name.
+ #
+ # class Post < ActiveRecord::Base
+ # scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))
+ # end
def arel_table
@arel_table ||= Arel::Table.new(table_name, arel_engine)
end
+ # Returns the Arel engine.
def arel_engine
@arel_engine ||= connection_handler.retrieve_connection_pool(self) ? self : active_record_super.arel_engine
end
@@ -205,13 +211,34 @@ module ActiveRecord
self
end
+ ##
+ # :method: clone
+ # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
+ # That means that modifying attributes of the clone will modify the original, since they will both point to the
+ # same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
+ #
+ # user = User.first
+ # new_user = user.clone
+ # user.name # => "Bob"
+ # new_user.name = "Joe"
+ # user.name # => "Joe"
+ #
+ # user.object_id == new_user.object_id # => false
+ # user.name.object_id == new_user.name.object_id # => true
+ #
+ # user.name.object_id == user.dup.name.object_id # => false
+
+ ##
+ # :method: dup
# Duped objects have no id assigned and are treated as new records. Note
# that this is a "shallow" copy as it copies the object's attributes
# only, not its associations. The extent of a "deep" copy is application
# specific and is therefore left to the application to implement according
# to its need.
# The dup method does not preserve the timestamps (created|updated)_(at|on).
- def initialize_dup(other)
+
+ ##
+ def initialize_dup(other) # :nodoc:
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
self.class.initialize_attributes(cloned_attributes)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index a01e2f74ff..7e6512501c 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -83,7 +83,7 @@ module ActiveRecord
# end
#
# test "find_alt_method_2" do
- # assert_equal "Ruby on Rails", @rubyonrails.news
+ # assert_equal "Ruby on Rails", @rubyonrails.name
# end
#
# In order to use these methods to access fixtured data within your testcases, you must specify one of the
@@ -372,19 +372,23 @@ module ActiveRecord
#
# Any fixture labeled "DEFAULTS" is safely ignored.
class Fixtures
+ #--
+ # NOTE: an instance of Fixtures can be called fixture_set, it is normally stored in a single YAML file and possibly in a folder with the same name.
+ #++
MAX_ID = 2 ** 30 - 1
@@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
- def self.default_fixture_model_name(fixture_name) # :nodoc:
+ def self.default_fixture_model_name(fixture_set_name) # :nodoc:
ActiveRecord::Base.pluralize_table_names ?
- fixture_name.singularize.camelize :
- fixture_name.camelize
+ fixture_set_name.singularize.camelize :
+ fixture_set_name.camelize
end
- def self.default_fixture_table_name(fixture_name) # :nodoc:
- "#{ActiveRecord::Base.table_name_prefix}"\
- "#{fixture_name.tr('/', '_')}#{ActiveRecord::Base.table_name_suffix}".to_sym
+ def self.default_fixture_table_name(fixture_set_name) # :nodoc:
+ "#{ ActiveRecord::Base.table_name_prefix }"\
+ "#{ fixture_set_name.tr('/', '_') }"\
+ "#{ ActiveRecord::Base.table_name_suffix }".to_sym
end
def self.reset_cache
@@ -411,11 +415,11 @@ module ActiveRecord
cache_for_connection(connection).update(fixtures_map)
end
- def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
+ def self.instantiate_fixtures(object, fixture_set, load_instances = true)
if load_instances
- fixtures.each do |name, fixture|
+ fixture_set.each do |fixture_name, fixture|
begin
- object.instance_variable_set "@#{name}", fixture.find
+ object.instance_variable_set "@#{fixture_name}", fixture.find
rescue FixtureClassNotFound
nil
end
@@ -424,61 +428,59 @@ module ActiveRecord
end
def self.instantiate_all_loaded_fixtures(object, load_instances = true)
- all_loaded_fixtures.each do |table_name, fixtures|
- ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ all_loaded_fixtures.each_value do |fixture_set|
+ instantiate_fixtures(object, fixture_set, load_instances)
end
end
cattr_accessor :all_loaded_fixtures
self.all_loaded_fixtures = {}
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
- table_names = Array(table_names).map(&:to_s)
+ def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {})
+ fixture_set_names = Array(fixture_set_names).map(&:to_s)
class_names = class_names.stringify_keys
# FIXME: Apparently JK uses this.
connection = block_given? ? yield : ActiveRecord::Base.connection
- files_to_read = table_names.reject { |table_name|
- fixture_is_cached?(connection, table_name)
+ files_to_read = fixture_set_names.reject { |fs_name|
+ fixture_is_cached?(connection, fs_name)
}
unless files_to_read.empty?
connection.disable_referential_integrity do
fixtures_map = {}
- fixture_files = files_to_read.map do |path|
- fixture_name = path
-
- fixtures_map[fixture_name] = new( # ActiveRecord::Fixtures.new
+ fixture_sets = files_to_read.map do |fs_name|
+ fixtures_map[fs_name] = new( # ActiveRecord::Fixtures.new
connection,
- fixture_name,
- class_names[fixture_name.to_s] || default_fixture_model_name(fixture_name),
- ::File.join(fixtures_directory, path))
+ fs_name,
+ class_names[fs_name] || default_fixture_model_name(fs_name),
+ ::File.join(fixtures_directory, fs_name))
end
all_loaded_fixtures.update(fixtures_map)
connection.transaction(:requires_new => true) do
- fixture_files.each do |ff|
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
- table_rows = ff.table_rows
+ fixture_sets.each do |fs|
+ conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
+ table_rows = fs.table_rows
table_rows.keys.each do |table|
conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
end
- table_rows.each do |table_name,rows|
+ table_rows.each do |fixture_set_name, rows|
rows.each do |row|
- conn.insert_fixture(row, table_name)
+ conn.insert_fixture(row, fixture_set_name)
end
end
end
# Cap primary key sequences to max(pk).
if connection.respond_to?(:reset_pk_sequence!)
- fixture_files.each do |ff|
- connection.reset_pk_sequence!(ff.table_name)
+ fixture_sets.each do |fs|
+ connection.reset_pk_sequence!(fs.table_name)
end
end
end
@@ -486,7 +488,7 @@ module ActiveRecord
cache_fixtures(connection, fixtures_map)
end
end
- cached_fixtures(connection, table_names)
+ cached_fixtures(connection, fixture_set_names)
end
# Returns a consistent, platform-independent identifier for +label+.
@@ -497,26 +499,23 @@ module ActiveRecord
attr_reader :table_name, :name, :fixtures, :model_class
- def initialize(connection, fixture_name, class_name, fixture_path)
- @connection = connection
- @fixture_path = fixture_path
- @name = fixture_name
- @class_name = class_name
-
- @fixtures = {}
+ def initialize(connection, name, class_name, path)
+ @fixtures = {} # Ordered hash
+ @name = name
+ @path = path
- # Should be an AR::Base type class
- if class_name.is_a?(Class)
+ if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
@model_class = class_name
else
@model_class = class_name.constantize rescue nil
end
- @connection = model_class.connection if model_class && model_class.respond_to?(:connection)
+ @connection = ( model_class.respond_to?(:connection) ?
+ model_class.connection : connection )
@table_name = ( model_class.respond_to?(:table_name) ?
model_class.table_name :
- self.class.default_fixture_table_name(fixture_name) )
+ self.class.default_fixture_table_name(name) )
read_fixture_files
end
@@ -555,8 +554,8 @@ module ActiveRecord
if model_class && model_class < ActiveRecord::Model
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
if model_class.record_timestamps
- timestamp_column_names.each do |name|
- row[name] = now unless row.key?(name)
+ timestamp_column_names.each do |c_name|
+ row[c_name] = now unless row.key?(c_name)
end
end
@@ -634,26 +633,23 @@ module ActiveRecord
end
def read_fixture_files
- yaml_files = Dir["#{@fixture_path}/**/*.yml"].select { |f|
+ yaml_files = Dir["#{@path}/**/*.yml"].select { |f|
::File.file?(f)
} + [yaml_file_path]
yaml_files.each do |file|
Fixtures::File.open(file) do |fh|
- fh.each do |name, row|
- fixtures[name] = ActiveRecord::Fixture.new(row, model_class)
+ fh.each do |fixture_name, row|
+ fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
end
end
end
end
def yaml_file_path
- "#{@fixture_path}.yml"
+ "#{@path}.yml"
end
- def yaml_fixtures_key(path)
- ::File.basename(@fixture_path).split(".").first
- end
end
class Fixture #:nodoc:
@@ -708,7 +704,7 @@ module ActiveRecord
class_attribute :fixture_table_names
class_attribute :fixture_class_names
class_attribute :use_transactional_fixtures
- class_attribute :use_instantiated_fixtures # true, false, or :no_instances
+ class_attribute :use_instantiated_fixtures # true, false, or :no_instances
class_attribute :pre_loaded_fixtures
self.fixture_table_names = []
@@ -716,9 +712,8 @@ module ActiveRecord
self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
- self.fixture_class_names = Hash.new do |h, fixture_name|
- fixture_name = fixture_name.to_s
- h[fixture_name] = ActiveRecord::Fixtures.default_fixture_model_name(fixture_name)
+ self.fixture_class_names = Hash.new do |h, fixture_set_name|
+ h[fixture_set_name] = ActiveRecord::Fixtures.default_fixture_model_name(fixture_set_name)
end
end
@@ -742,18 +737,18 @@ module ActiveRecord
self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
end
- def fixtures(*fixture_names)
- if fixture_names.first == :all
- fixture_names = Dir["#{fixture_path}/**/*.yml"].map { |f|
+ def fixtures(*fixture_set_names)
+ if fixture_set_names.first == :all
+ fixture_set_names = Dir["#{fixture_path}/**/*.yml"].map { |f|
File.basename f, '.yml'
}
else
- fixture_names = fixture_names.flatten.map { |n| n.to_s }
+ fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
end
- self.fixture_table_names |= fixture_names
- require_fixture_classes(fixture_names)
- setup_fixture_accessors(fixture_names)
+ self.fixture_table_names |= fixture_set_names
+ require_fixture_classes(fixture_set_names)
+ setup_fixture_accessors(fixture_set_names)
end
def try_to_load_dependency(file_name)
@@ -768,33 +763,39 @@ module ActiveRecord
end
end
- def require_fixture_classes(fixture_names = nil)
- (fixture_names || fixture_table_names).each do |fixture_name|
- file_name = fixture_name.to_s
+ def require_fixture_classes(fixture_set_names = nil)
+ if fixture_set_names
+ fixture_set_names = fixture_set_names.map { |n| n.to_s }
+ else
+ fixture_set_names = fixture_table_names
+ end
+
+ fixture_set_names.each do |file_name|
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
try_to_load_dependency(file_name)
end
end
- def setup_fixture_accessors(fixture_names = nil)
- fixture_names = Array(fixture_names || fixture_table_names)
+ def setup_fixture_accessors(fixture_set_names = nil)
+ fixture_set_names = Array(fixture_set_names || fixture_table_names)
methods = Module.new do
- fixture_names.each do |fixture_name|
- fixture_name = fixture_name.to_s
- accessor_name = fixture_name.tr('/', '_').to_sym
+ fixture_set_names.each do |fs_name|
+ fs_name = fs_name.to_s
+ accessor_name = fs_name.tr('/', '_').to_sym
- define_method(accessor_name) do |*fixtures|
- force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
+ define_method(accessor_name) do |*fixture_names|
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
- @fixture_cache[fixture_name] ||= {}
+ @fixture_cache[fs_name] ||= {}
- instances = fixtures.map do |fixture|
- @fixture_cache[fixture_name].delete(fixture) if force_reload
+ instances = fixture_names.map do |f_name|
+ f_name = f_name.to_s
+ @fixture_cache[fs_name].delete(f_name) if force_reload
- if @loaded_fixtures[fixture_name][fixture.to_s]
- @fixture_cache[fixture_name][fixture] ||= @loaded_fixtures[fixture_name][fixture.to_s].find
+ if @loaded_fixtures[fs_name][f_name]
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
else
- raise StandardError, "No entry named '#{fixture}' found for fixture collection '#{fixture_name}'"
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
end
end
@@ -901,8 +902,8 @@ module ActiveRecord
ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
else
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
- @loaded_fixtures.each do |fixture_name, fixtures|
- ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
+ @loaded_fixtures.each_value do |fixture_set|
+ ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_set, load_instances?)
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 32a1dae6bc..95a2ddcc11 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -19,10 +19,10 @@ module ActiveRecord
# = Active Record Nested Attributes
#
# Nested attributes allow you to save attributes on associated records
- # through the parent. By default nested attribute updating is turned off,
- # you can enable it using the accepts_nested_attributes_for class method.
- # When you enable nested attributes an attribute writer is defined on
- # the model.
+ # through the parent. By default nested attribute updating is turned off
+ # and you can enable it using the accepts_nested_attributes_for class
+ # method. When you enable nested attributes an attribute writer is
+ # defined on the model.
#
# The attribute writer is named after the association, which means that
# in the following example, two new methods are added to your model:
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 4a987c2343..a1bc39a32d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -123,6 +123,7 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
def destroy
+ raise ReadOnlyRecord if readonly?
destroy_associations
destroy_row if persisted?
@destroyed = true
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 3ce9995031..31d99f0192 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -108,34 +108,57 @@ module ActiveRecord
0
end
- # This method is designed to perform select by a single column as direct SQL query
- # Returns <tt>Array</tt> with values of the specified column name
- # The values has same data type as column.
+ # Use <tt>pluck</tt> as a shortcut to select a single attribute without
+ # loading a bunch of records just to grab one attribute you want.
+ #
+ # Person.pluck(:name)
+ #
+ # instead of
+ #
+ # Person.all.map(&:name)
+ #
+ # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
+ # the plucked column name, if it can be deduced. Plucking a SQL fragment
+ # returns String values by default.
#
# Examples:
#
- # Person.pluck(:id) # SELECT people.id FROM people
- # Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
- # Person.where(:age => 21).limit(5).pluck(:id) # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
+ # Person.pluck(:id)
+ # # SELECT people.id FROM people
+ # # => [1, 2, 3]
+ #
+ # Person.uniq.pluck(:role)
+ # # SELECT DISTINCT role FROM people
+ # # => ['admin', 'member', 'guest']
+ #
+ # Person.where(:age => 21).limit(5).pluck(:id)
+ # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
+ # # => [2, 3]
+ #
+ # Person.pluck('DATEDIFF(updated_at, created_at)')
+ # # SELECT DATEDIFF(updated_at, created_at) FROM people
+ # # => ['0', '27761', '173']
#
def pluck(column_name)
- key = column_name.to_s.split('.', 2).last
-
if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
column_name = "#{table_name}.#{column_name}"
end
result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
- types = result.column_types.merge klass.column_types
- column = types[key]
+
+ key = result.columns.first
+ column = klass.column_types.fetch(key) {
+ result.column_types.fetch(key) {
+ Class.new { def type_cast(v); v; end }.new
+ }
+ }
result.map do |attributes|
- value = klass.initialize_attributes(attributes)[key]
- if column
- column.type_cast value
- else
- value
- end
+ raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?
+
+ value = klass.initialize_attributes(attributes).values.first
+
+ column.type_cast(value)
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index cc716bbfd1..4fedd33d64 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -170,9 +170,8 @@ module ActiveRecord
# Person.exists?(['name LIKE ?', "%#{query}%"])
# Person.exists?
def exists?(id = false)
- return false if id.nil?
-
id = id.id if ActiveRecord::Model === id
+ return false if id.nil?
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 3f880ce5e9..36f98c6480 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -43,7 +43,7 @@ module ActiveRecord
def normal_values
Relation::SINGLE_VALUE_METHODS +
Relation::MULTI_VALUE_METHODS -
- [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering]
+ [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from]
end
def merge
@@ -76,6 +76,7 @@ module ActiveRecord
end
def merge_single_values
+ relation.from_value = values[:from] unless relation.from_value
relation.lock_value = values[:lock] unless relation.lock_value
relation.reverse_order_value = values[:reverse_order]
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index b40bf2b3cf..6a0cdd5917 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -34,9 +34,6 @@ module ActiveRecord
private
def self.build(attribute, value)
case value
- when ActiveRecord::Relation
- value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
- attribute.in(value.arel.ast)
when Array, ActiveRecord::Associations::CollectionProxy
values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x}
ranges, values = values.partition {|v| v.is_a?(Range)}
@@ -59,6 +56,9 @@ module ActiveRecord
array_predicates = ranges.map { |range| attribute.in(range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
+ when ActiveRecord::Relation
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
+ attribute.in(value.arel.ast)
when Range
attribute.in(value)
when ActiveRecord::Model
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 855477eaed..19fe8155d9 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -40,7 +40,7 @@ module ActiveRecord
alias extensions extending_values
def includes(*args)
- args.empty? ? self : clone.includes!(*args)
+ args.empty? ? self : spawn.includes!(*args)
end
def includes!(*args)
@@ -51,7 +51,7 @@ module ActiveRecord
end
def eager_load(*args)
- args.blank? ? self : clone.eager_load!(*args)
+ args.blank? ? self : spawn.eager_load!(*args)
end
def eager_load!(*args)
@@ -60,7 +60,7 @@ module ActiveRecord
end
def preload(*args)
- args.blank? ? self : clone.preload!(*args)
+ args.blank? ? self : spawn.preload!(*args)
end
def preload!(*args)
@@ -79,7 +79,7 @@ module ActiveRecord
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
# # => Query now knows the string references posts, so adds a JOIN
def references(*args)
- args.blank? ? self : clone.references!(*args)
+ args.blank? ? self : spawn.references!(*args)
end
def references!(*args)
@@ -120,7 +120,7 @@ module ActiveRecord
if block_given?
to_a.select { |*block_args| value.call(*block_args) }
else
- clone.select!(value)
+ spawn.select!(value)
end
end
@@ -130,7 +130,7 @@ module ActiveRecord
end
def group(*args)
- args.blank? ? self : clone.group!(*args)
+ args.blank? ? self : spawn.group!(*args)
end
def group!(*args)
@@ -139,7 +139,7 @@ module ActiveRecord
end
def order(*args)
- args.blank? ? self : clone.order!(*args)
+ args.blank? ? self : spawn.order!(*args)
end
def order!(*args)
@@ -165,7 +165,7 @@ module ActiveRecord
# generates a query with 'ORDER BY id ASC, name ASC'.
#
def reorder(*args)
- args.blank? ? self : clone.reorder!(*args)
+ args.blank? ? self : spawn.reorder!(*args)
end
def reorder!(*args)
@@ -175,7 +175,7 @@ module ActiveRecord
end
def joins(*args)
- args.compact.blank? ? self : clone.joins!(*args)
+ args.compact.blank? ? self : spawn.joins!(*args)
end
def joins!(*args)
@@ -186,7 +186,7 @@ module ActiveRecord
end
def bind(value)
- clone.bind!(value)
+ spawn.bind!(value)
end
def bind!(value)
@@ -195,7 +195,7 @@ module ActiveRecord
end
def where(opts, *rest)
- opts.blank? ? self : clone.where!(opts, *rest)
+ opts.blank? ? self : spawn.where!(opts, *rest)
end
def where!(opts, *rest)
@@ -206,7 +206,7 @@ module ActiveRecord
end
def having(opts, *rest)
- opts.blank? ? self : clone.having!(opts, *rest)
+ opts.blank? ? self : spawn.having!(opts, *rest)
end
def having!(opts, *rest)
@@ -217,7 +217,7 @@ module ActiveRecord
end
def limit(value)
- clone.limit!(value)
+ spawn.limit!(value)
end
def limit!(value)
@@ -226,7 +226,7 @@ module ActiveRecord
end
def offset(value)
- clone.offset!(value)
+ spawn.offset!(value)
end
def offset!(value)
@@ -235,7 +235,7 @@ module ActiveRecord
end
def lock(locks = true)
- clone.lock!(locks)
+ spawn.lock!(locks)
end
def lock!(locks = true)
@@ -283,7 +283,7 @@ module ActiveRecord
end
def readonly(value = true)
- clone.readonly!(value)
+ spawn.readonly!(value)
end
def readonly!(value = true)
@@ -292,7 +292,7 @@ module ActiveRecord
end
def create_with(value)
- clone.create_with!(value)
+ spawn.create_with!(value)
end
def create_with!(value)
@@ -300,12 +300,25 @@ module ActiveRecord
self
end
- def from(value)
- clone.from!(value)
+ # Specifies table from which the records will be fetched. For example:
+ #
+ # Topic.select('title').from('posts')
+ # #=> SELECT title FROM posts
+ #
+ # Can accept other relation objects. For example:
+ #
+ # Topic.select('title').from(Topics.approved)
+ # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
+ #
+ # Topics.select('a.title').from(Topics.approved, :a)
+ # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
+ #
+ def from(value, subquery_name = nil)
+ spawn.from!(value, subquery_name)
end
- def from!(value)
- self.from_value = value
+ def from!(value, subquery_name = nil)
+ self.from_value = [value, subquery_name]
self
end
@@ -320,7 +333,7 @@ module ActiveRecord
# User.select(:name).uniq.uniq(false)
# # => You can also remove the uniqueness
def uniq(value = true)
- clone.uniq!(value)
+ spawn.uniq!(value)
end
def uniq!(value = true)
@@ -366,7 +379,7 @@ module ActiveRecord
# end
def extending(*modules, &block)
if modules.any? || block
- clone.extending!(*modules, &block)
+ spawn.extending!(*modules, &block)
else
self
end
@@ -382,7 +395,7 @@ module ActiveRecord
end
def reverse_order
- clone.reverse_order!
+ spawn.reverse_order!
end
def reverse_order!
@@ -415,7 +428,7 @@ module ActiveRecord
build_select(arel, select_values.uniq)
arel.distinct(uniq_value)
- arel.from(from_value) if from_value
+ arel.from(build_from) if from_value
arel.lock(lock_value) if lock_value
arel
@@ -464,6 +477,17 @@ module ActiveRecord
end
end
+ def build_from
+ opts, name = from_value
+ case opts
+ when Relation
+ name ||= 'subquery'
+ opts.arel.as(name.to_s)
+ else
+ opts
+ end
+ end
+
def build_joins(manager, joins)
buckets = joins.group_by do |join|
case join
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index f6d178db7a..80d087a9ea 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -5,7 +5,12 @@ require 'active_record/relation/merger'
module ActiveRecord
module SpawnMethods
-
+
+ # This is overridden by Associations::CollectionProxy
+ def spawn #:nodoc:
+ clone
+ end
+
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
#
@@ -23,7 +28,7 @@ module ActiveRecord
if other.is_a?(Array)
to_a & other
elsif other
- clone.merge!(other)
+ spawn.merge!(other)
else
self
end
@@ -42,7 +47,7 @@ module ActiveRecord
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
#
def except(*skips)
- result = self.class.new(@klass, table, values.except(*skips))
+ result = Relation.new(klass, table, values.except(*skips))
result.default_scoped = default_scoped
result.extend(*extending_values) if extending_values.any?
result
@@ -56,7 +61,7 @@ module ActiveRecord
# Post.order('id asc').only(:where, :order) # uses the specified order
#
def only(*onlies)
- result = self.class.new(@klass, table, values.slice(*onlies))
+ result = Relation.new(klass, table, values.slice(*onlies))
result.default_scoped = default_scoped
result.extend(*extending_values) if extending_values.any?
result
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index d815ab05ac..599e68379a 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -34,6 +34,15 @@ module ActiveRecord
ActiveRecord::Migrator.migrations_paths
end
+ def define(info, &block)
+ instance_eval(&block)
+
+ unless info[:version].blank?
+ initialize_schema_migrations_table
+ assume_migrated_upto_version(info[:version], migrations_paths)
+ end
+ end
+
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (+create_table+,
@@ -46,13 +55,7 @@ module ActiveRecord
# ...
# end
def self.define(info={}, &block)
- schema = new
- schema.instance_eval(&block)
-
- unless info[:version].blank?
- initialize_schema_migrations_table
- assume_migrated_upto_version(info[:version], schema.migrations_paths)
- end
+ new.define(info, &block)
end
end
end
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index ed47a26749..5a256b040b 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -201,10 +201,10 @@ module ActiveRecord
class << self
alias :data_column_name :data_column
-
+
# Use the ActiveRecord::Base.connection by default.
attr_writer :connection
-
+
# Use the ActiveRecord::Base.connection_pool by default.
attr_writer :connection_pool
@@ -218,12 +218,12 @@ module ActiveRecord
# Look up a session by id and unmarshal its data if found.
def find_by_session_id(session_id)
- if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
+ if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id.to_s)}")
new(:session_id => session_id, :marshaled_data => record['data'])
end
end
end
-
+
delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self
attr_reader :session_id, :new_record
@@ -241,6 +241,11 @@ module ActiveRecord
@new_record = @marshaled_data.nil?
end
+ # Returns true if the record is persisted, i.e. it's not a new record
+ def persisted?
+ !@new_record
+ end
+
# Lazy-unmarshal session state.
def data
unless @data
@@ -287,7 +292,7 @@ module ActiveRecord
connect = connection
connect.delete <<-end_sql, 'Destroy session'
DELETE FROM #{table_name}
- WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id.to_s)}
end_sql
end
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 1c7b839e5e..ce2ea85ef9 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -10,15 +10,21 @@ module ActiveRecord
# Make sure that you declare the database column used for the serialized store as a text, so there's
# plenty of room.
#
+ # You can set custom coder to encode/decode your serialized attributes to/from different formats.
+ # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
+ #
+ # String keys should be used for direct access to virtual attributes because of most of the coders do not
+ # distinguish symbols and strings as keys.
+ #
# Examples:
#
# class User < ActiveRecord::Base
- # store :settings, accessors: [ :color, :homepage ]
+ # store :settings, accessors: [ :color, :homepage ], coder: JSON
# end
#
# u = User.new(color: 'black', homepage: '37signals.com')
- # u.color # Accessor stored attribute
- # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+ # u.color # Accessor stored attribute
+ # u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor
#
# # Add additional accessors to an existing store through store_accessor
# class SuperUser < User
@@ -29,7 +35,7 @@ module ActiveRecord
module ClassMethods
def store(store_attribute, options = {})
- serialize store_attribute, Hash
+ serialize store_attribute, options.fetch(:coder, Hash)
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
end
@@ -37,13 +43,13 @@ module ActiveRecord
keys.flatten.each do |key|
define_method("#{key}=") do |value|
send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
- send(store_attribute)[key] = value
+ send(store_attribute)[key.to_s] = value
send("#{store_attribute}_will_change!")
end
define_method(key) do
send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
- send(store_attribute)[key]
+ send(store_attribute)[key.to_s]
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 64e5640791..30e1035300 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -290,7 +290,15 @@ module ActiveRecord
status = nil
self.class.transaction do
add_to_transaction
- status = yield
+ begin
+ status = yield
+ rescue ActiveRecord::Rollback
+ if defined?(@_start_transaction_state)
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ end
+ status = nil
+ end
+
raise ActiveRecord::Rollback unless status
end
status