aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb20
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb16
-rw-r--r--activerecord/lib/active_record/base.rb122
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/identity_map.rb40
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb9
-rw-r--r--activerecord/lib/active_record/query_cache.rb28
-rw-r--r--activerecord/lib/active_record/railtie.rb10
-rw-r--r--activerecord/lib/active_record/railties/console_sandbox.rb6
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb36
-rw-r--r--activerecord/test/cases/identity_map/middleware_test.rb71
-rw-r--r--activerecord/test/cases/identity_map_test.rb10
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb12
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb430
-rw-r--r--activerecord/test/cases/query_cache_test.rb64
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb20
-rw-r--r--activerecord/test/models/developer.rb24
-rw-r--r--activerecord/test/models/person.rb23
-rw-r--r--activerecord/test/schema/schema.rb2
27 files changed, 841 insertions, 193 deletions
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 9d4cbbc150..3a5035305b 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -23,5 +23,5 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('arel', '~> 2.1.0')
- s.add_dependency('tzinfo', '~> 0.3.23')
+ s.add_dependency('tzinfo', '~> 0.3.27')
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 33a184d48d..6cdec8c487 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -93,20 +93,20 @@ module ActiveRecord
first_or_last(:last, *args)
end
- def build(attributes = {}, &block)
- build_or_create(attributes, :build, &block)
+ def build(attributes = {}, options = {}, &block)
+ build_or_create(:build, attributes, options, &block)
end
- def create(attributes = {}, &block)
+ def create(attributes = {}, options = {}, &block)
unless owner.persisted?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
- build_or_create(attributes, :create, &block)
+ build_or_create(:create, attributes, options, &block)
end
- def create!(attrs = {}, &block)
- record = create(attrs, &block)
+ def create!(attrs = {}, options = {}, &block)
+ record = create(attrs, options, &block)
Array.wrap(record).each(&:save!)
record
end
@@ -403,9 +403,9 @@ module ActiveRecord
end + existing
end
- def build_or_create(attributes, method)
+ def build_or_create(method, attributes, options)
records = Array.wrap(attributes).map do |attrs|
- record = build_record(attrs)
+ record = build_record(attrs, options)
add_to_target(record) do
yield(record) if block_given?
@@ -421,8 +421,8 @@ module ActiveRecord
raise NotImplementedError
end
- def build_record(attributes)
- reflection.build_association(scoped.scope_for_create.merge(attributes))
+ def build_record(attributes, options)
+ reflection.build_association(scoped.scope_for_create.merge(attributes), options)
end
def delete_or_destroy(records, method)
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 9d2b29685b..7708228d23 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -60,10 +60,10 @@ module ActiveRecord
through_record
end
- def build_record(attributes)
+ def build_record(attributes, options = {})
ensure_not_nested
- record = super(attributes)
+ record = super(attributes, options)
inverse = source_reflection.inverse_of
if inverse
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 4edbe216be..ea4d73d414 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -17,16 +17,16 @@ module ActiveRecord
replace(record)
end
- def create(attributes = {})
- new_record(:create, attributes)
+ def create(attributes = {}, options = {})
+ new_record(:create, attributes, options)
end
- def create!(attributes = {})
- build(attributes).tap { |record| record.save! }
+ def create!(attributes = {}, options = {})
+ build(attributes, options).tap { |record| record.save! }
end
- def build(attributes = {})
- new_record(:build, attributes)
+ def build(attributes = {}, options = {})
+ new_record(:build, attributes, options)
end
private
@@ -44,9 +44,9 @@ module ActiveRecord
replace(record)
end
- def new_record(method, attributes)
+ def new_record(method, attributes, options)
attributes = scoped.scope_for_create.merge(attributes || {})
- record = reflection.send("#{method}_association", attributes)
+ record = reflection.send("#{method}_association", attributes, options)
set_new_record(record)
record
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 04c12f86b6..58a056bce9 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -83,7 +83,7 @@ module ActiveRecord #:nodoc:
#
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
- # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
+ # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
# before inserting them in the query, which will ensure that an attacker can't escape the
# query and fake the login (or worse).
@@ -406,10 +406,10 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
# Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
# ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
+ # supports migrations. Use :ruby if you want to have different database
# adapters for, e.g., your development and test environments.
cattr_accessor :schema_format , :instance_writer => false
@@schema_format = :ruby
@@ -443,17 +443,17 @@ module ActiveRecord #:nodoc:
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
- # Executes a custom SQL query against your database and returns all the results. The results will
+ # Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
- # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
# a Product object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
# table.
#
- # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
- # no database agnostic conversions performed. This should be a last resort because using, for example,
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
# MySQL specific terms will lock you to using that particular database engine or require you to
# change your call if you switch engines.
#
@@ -472,13 +472,22 @@ module ActiveRecord #:nodoc:
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
# The resulting object is returned whether the object was saved successfully to the database or not.
#
- # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
# attributes on the objects that are to be created.
#
+ # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
+ # in the +options+ parameter.
+ #
# ==== Examples
# # Create a single new object
# User.create(:first_name => 'Jamie')
#
+ # # Create a single new object using the :admin mass-assignment security scope
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
+ #
+ # # Create a single new object bypassing mass-assignment security
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
+ #
# # Create an Array of new objects
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
#
@@ -491,11 +500,11 @@ module ActiveRecord #:nodoc:
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
# u.is_admin = false
# end
- def create(attributes = nil, &block)
+ def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
- attributes.collect { |attr| create(attr, &block) }
+ attributes.collect { |attr| create(attr, options, &block) }
else
- object = new(attributes)
+ object = new(attributes, options)
yield(object) if block_given?
object.save
object
@@ -504,7 +513,7 @@ module ActiveRecord #:nodoc:
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
# The use of this method should be restricted to complicated SQL queries that can't be executed
- # using the ActiveRecord::Calculations class methods. Look into those before using this.
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
#
# ==== Parameters
#
@@ -581,7 +590,7 @@ module ActiveRecord #:nodoc:
# invoice/lineitem.rb Invoice::Lineitem lineitems
#
# Additionally, the class-level +table_name_prefix+ is prepended and the
- # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
# the table name guess for an Invoice class becomes "myapp_invoices".
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
#
@@ -615,7 +624,7 @@ module ActiveRecord #:nodoc:
@inheritance_column ||= "type"
end
- # Lazy-set the sequence name to the connection's default. This method
+ # Lazy-set the sequence name to the connection's default. This method
# is only ever called once since set_sequence_name overrides it.
def sequence_name #:nodoc:
reset_sequence_name
@@ -627,7 +636,7 @@ module ActiveRecord #:nodoc:
default
end
- # Sets the table name. If the value is nil or false then the value returned by the given
+ # Sets the table name. If the value is nil or false then the value returned by the given
# block is used.
#
# class Project < ActiveRecord::Base
@@ -1077,7 +1086,7 @@ module ActiveRecord #:nodoc:
# <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
#
# <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
- # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
+ # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
# array of strings format for your joins.
#
# class Article < ActiveRecord::Base
@@ -1180,19 +1189,15 @@ MSG
# Use this macro in your model to set a default scope for all operations on
# the model.
#
- # class Person < ActiveRecord::Base
- # default_scope order('last_name, first_name')
+ # class Article < ActiveRecord::Base
+ # default_scope where(:published => true)
# end
#
- # Person.all # => SELECT * FROM people ORDER BY last_name, first_name
+ # Article.all # => SELECT * FROM articles WHERE published = true
#
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
# applied while updating a record.
#
- # class Article < ActiveRecord::Base
- # default_scope where(:published => true)
- # end
- #
# Article.new.published # => true
# Article.create.published # => true
#
@@ -1205,6 +1210,19 @@ MSG
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
# macro, and it will be called when building the default scope.)
#
+ # If you use multiple <tt>default_scope</tt> declarations in your model then they will
+ # be merged together:
+ #
+ # class Article < ActiveRecord::Base
+ # default_scope where(:published => true)
+ # default_scope where(:rating => 'G')
+ # end
+ #
+ # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
+ #
+ # This is also the case with inheritance and module includes where the parent or module
+ # defines a <tt>default_scope</tt> and the child or including class defines a second one.
+ #
# If you need to do more complex things with a default scope, you can alternatively
# define it as a class method:
#
@@ -1214,36 +1232,8 @@ MSG
# end
# end
def default_scope(scope = {})
- if default_scopes.length != 0
- ActiveSupport::Deprecation.warn <<-WARN
-Calling 'default_scope' multiple times in a class (including when a superclass calls 'default_scope') is deprecated. The current behavior is that this will merge the default scopes together:
-
-class Post < ActiveRecord::Base # Rails 3.1
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:published => true, :hidden => false)
-end
-
-In Rails 3.2, the behavior will be changed to overwrite previous scopes:
-
-class Post < ActiveRecord::Base # Rails 3.2
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:hidden => false)
-end
-
-If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.):
-
-class Post < ActiveRecord::Base
- def self.default_scope
- where(:published => true).where(:hidden => false)
- end
-end
- WARN
- end
-
scope = Proc.new if block_given?
- self.default_scopes = default_scopes.dup << scope
+ self.default_scopes = default_scopes + [scope]
end
def build_default_scope #:nodoc:
@@ -1400,7 +1390,7 @@ end
end.join(', ')
end
- # Accepts an array of conditions. The array has each value
+ # Accepts an array of conditions. The array has each value
# sanitized and interpolated into the SQL statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
def sanitize_sql_array(ary)
@@ -1484,7 +1474,20 @@ end
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
# In both instances, valid attribute keys are determined by the column names of the associated table --
# hence you can't have attributes that aren't part of the table columns.
- def initialize(attributes = nil)
+ #
+ # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
+ # in the +options+ parameter.
+ #
+ # ==== Examples
+ # # Instantiates a single new object
+ # User.new(:first_name => 'Jamie')
+ #
+ # # Instantiates a single new object using the :admin mass-assignment security scope
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
+ #
+ # # Instantiates a single new object bypassing mass-assignment security
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
+ def initialize(attributes = nil, options = {})
@attributes = attributes_from_column_definition
@association_cache = {}
@aggregation_cache = {}
@@ -1500,7 +1503,8 @@ end
set_serialized_attributes
populate_with_current_scope_attributes
- self.attributes = attributes unless attributes.nil?
+
+ assign_attributes(attributes, options) if attributes
result = yield self if block_given?
run_callbacks :initialize
@@ -1508,7 +1512,7 @@ end
end
# Populate +coder+ with attributes about this record that should be
- # serialized. The structure of +coder+ defined in this method is
+ # serialized. The structure of +coder+ defined in this method is
# guaranteed to match the structure of +coder+ passed to the +init_with+
# method.
#
@@ -1523,8 +1527,8 @@ end
coder['attributes'] = attributes
end
- # Initialize an empty model object from +coder+. +coder+ must contain
- # the attributes necessary for initializing an empty model object. For
+ # Initialize an empty model object from +coder+. +coder+ must contain
+ # the attributes necessary for initializing an empty model object. For
# example:
#
# class Post < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 1db397f584..093c30aa42 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -29,6 +29,14 @@ module ActiveRecord
@query_cache_enabled = old
end
+ def enable_query_cache!
+ @query_cache_enabled = true
+ end
+
+ def disable_query_cache!
+ @query_cache_enabled = false
+ end
+
# Disable the query cache within the block.
def uncached
old, @query_cache_enabled = @query_cache_enabled, false
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 7ac48c6646..70a8f6bb58 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -104,7 +104,7 @@ module ActiveRecord
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
# Requests a maximum column length. This is number of characters for <tt>:string</tt> and
- # <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
+ # <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns.
# * <tt>:default</tt> -
# The column's default value. Use nil for NULL.
# * <tt>:null</tt> -
@@ -153,7 +153,7 @@ module ActiveRecord
# This method returns <tt>self</tt>.
#
# == Examples
- # # Assuming td is an instance of TableDefinition
+ # # Assuming +td+ is an instance of TableDefinition
# td.column(:granted, :boolean)
# # granted BOOLEAN
#
@@ -204,7 +204,7 @@ module ActiveRecord
# end
#
# There's a short-hand method for each of the type values declared at the top. And then there's
- # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes.
+ # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
@@ -351,7 +351,7 @@ module ActiveRecord
@base.index_exists?(@table_name, column_name, options)
end
- # Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
+ # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
# ===== Example
# t.timestamps
def timestamps
@@ -398,7 +398,7 @@ module ActiveRecord
@base.remove_index(@table_name, options)
end
- # Removes the timestamp columns (created_at and updated_at) from the table.
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
# ===== Example
# t.remove_timestamps
def remove_timestamps
@@ -412,7 +412,7 @@ module ActiveRecord
@base.rename_column(@table_name, column_name, new_column_name)
end
- # Adds a reference. Optionally adds a +type+ column.
+ # 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)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 468a2b106b..65024d76f8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -46,34 +46,34 @@ module ActiveRecord
@instrumenter = ActiveSupport::Notifications.instrumenter
end
- # Returns the human-readable name of the adapter. Use mixed case - one
+ # Returns the human-readable name of the adapter. Use mixed case - one
# can always use downcase if needed.
def adapter_name
'Abstract'
end
- # Does this adapter support migrations? Backend specific, as the
+ # Does this adapter support migrations? Backend specific, as the
# abstract adapter always returns +false+.
def supports_migrations?
false
end
# Can this adapter determine the primary key for tables not attached
- # to an Active Record class, such as join tables? Backend specific, as
+ # to an Active Record class, such as join tables? Backend specific, as
# the abstract adapter always returns +false+.
def supports_primary_key?
false
end
- # Does this adapter support using DISTINCT within COUNT? This is +true+
+ # Does this adapter support using DISTINCT within COUNT? This is +true+
# for all adapters except sqlite.
def supports_count_distinct?
true
end
- # Does this adapter support DDL rollbacks in transactions? That is, would
- # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
- # SQL Server, and others support this. MySQL and others do not.
+ # Does this adapter support DDL rollbacks in transactions? That is, would
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
+ # SQL Server, and others support this. MySQL and others do not.
def supports_ddl_transactions?
false
end
@@ -89,7 +89,7 @@ module ActiveRecord
end
# Should primary key values be selected from their corresponding
- # sequence before the insert statement? If true, next_sequence_value
+ # sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
# This is false for all adapters but Firebird.
def prefetch_primary_key?(table_name = nil)
@@ -149,7 +149,7 @@ module ActiveRecord
###
# Clear any caching the database adapter may be doing, for example
- # clearing the prepared statement cache. This is database specific.
+ # clearing the prepared statement cache. This is database specific.
def clear_cache!
# this should be overridden by concrete adapters
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index d3a054c29b..8af22fe9f5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -5,6 +5,7 @@ require 'mysql2'
module ActiveRecord
class Base
+ # Establishes a connection to the database that's used by all Active Record objects.
def self.mysql2_connection(config)
config[:username] = 'root' if config[:username].nil?
@@ -213,6 +214,8 @@ module ActiveRecord
false
end
+ # Disconnects from the database if already connected.
+ # Otherwise, this method does nothing.
def disconnect!
unless @connection.nil?
@connection.close
@@ -368,6 +371,8 @@ module ActiveRecord
end
end
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
def recreate_database(name, options = {})
drop_database(name)
create_database(name, options)
@@ -410,12 +415,25 @@ module ActiveRecord
show_variable 'collation_database'
end
- def tables(name = nil)
- tables = []
- execute("SHOW TABLES", name).each do |field|
- tables << field.first
+ def tables(name = nil, database = nil) #:nodoc:
+ sql = ["SHOW TABLES", database].compact.join(' IN ')
+ execute(sql, 'SCHEMA').collect do |field|
+ field.first
end
- tables
+ end
+
+ def table_exists?(name)
+ return true if super
+
+ name = name.to_s
+ schema, table = name.split('.', 2)
+
+ unless table # A table was provided without a schema
+ table = schema
+ schema = nil
+ end
+
+ tables(nil, schema).include? table
end
def drop_table(table_name, options = {})
@@ -426,7 +444,7 @@ module ActiveRecord
def indexes(table_name, name = nil)
indexes = []
current_index = nil
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) do |row|
if current_index != row[:Key_name]
next if row[:Key_name] == PRIMARY # skip the primary key
@@ -444,7 +462,7 @@ module ActiveRecord
def columns(table_name, name = nil)
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = []
- result = execute(sql)
+ result = execute(sql, 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) { |field|
columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
}
@@ -538,6 +556,7 @@ module ActiveRecord
end
end
+ # SHOW VARIABLES LIKE 'name'.
def show_variable(name)
variables = select_all("SHOW VARIABLES LIKE '#{name}'")
variables.first['Value'] unless variables.empty?
@@ -546,7 +565,7 @@ module ActiveRecord
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table)
keys = []
- result = execute("describe #{quote_table_name(table)}")
+ result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) do |row|
keys << row[:Field] if row[:Key] == "PRI"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 862ce852e6..a9f4c08348 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -517,6 +517,8 @@ module ActiveRecord
end.join("")
end
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
def recreate_database(name, options = {}) #:nodoc:
drop_database(name)
create_database(name, options)
@@ -560,9 +562,8 @@ module ActiveRecord
end
def tables(name = nil, database = nil) #:nodoc:
- tables = []
result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
- result.each { |field| tables << field[0] }
+ tables = result.collect { |field| field[0] }
result.free
tables
end
@@ -607,9 +608,8 @@ module ActiveRecord
# Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
def columns(table_name, name = nil)#:nodoc:
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
- columns = []
result = execute(sql, 'SCHEMA')
- result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
+ columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
result.free
columns
end
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index 95a8e5cff7..9eb47ad99f 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -49,12 +49,15 @@ module ActiveRecord
end
def get(klass, primary_key)
- obj = repository[klass.symbolized_base_class][primary_key]
- if obj.is_a?(klass)
- if ActiveRecord::Base.logger
- ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map"
- end
- obj
+ record = repository[klass.symbolized_base_class][primary_key]
+
+ if record.is_a?(klass)
+ ActiveSupport::Notifications.instrument("identity.active_record",
+ :line => "From Identity Map (id: #{primary_key})",
+ :name => "#{klass} Loaded",
+ :connection_id => object_id)
+
+ record
else
nil
end
@@ -95,14 +98,33 @@ module ActiveRecord
end
class Middleware
+ class Body #:nodoc:
+ def initialize(target, original)
+ @target = target
+ @original = original
+ end
+
+ def each(&block)
+ @target.each(&block)
+ end
+
+ def close
+ @target.close if @target.respond_to?(:close)
+ ensure
+ IdentityMap.enabled = @original
+ IdentityMap.clear
+ end
+ end
+
def initialize(app)
@app = app
end
def call(env)
- ActiveRecord::IdentityMap.use do
- @app.call(env)
- end
+ enabled = IdentityMap.enabled
+ IdentityMap.enabled = true
+ status, headers, body = @app.call(env)
+ [status, headers, Body.new(body, enabled)]
end
end
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index d31e321440..3a015ee8c2 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -46,6 +46,15 @@ module ActiveRecord
debug " #{name} #{sql}#{binds}"
end
+ def identity(event)
+ return unless logger.debug?
+
+ name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
+ line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
+
+ debug " #{name} #{line}"
+ end
+
def odd?
@odd_or_even = !@odd_or_even
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index d9f85a4e5e..4e61671473 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -27,10 +27,32 @@ module ActiveRecord
@app = app
end
- def call(env)
- ActiveRecord::Base.cache do
- @app.call(env)
+ class BodyProxy # :nodoc:
+ def initialize(original_cache_value, target)
+ @original_cache_value = original_cache_value
+ @target = target
+ end
+
+ def each(&block)
+ @target.each(&block)
+ end
+
+ def close
+ @target.close if @target.respond_to?(:close)
+ ensure
+ ActiveRecord::Base.connection.clear_query_cache
+ unless @original_cache_value
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
end
end
+
+ def call(env)
+ old = ActiveRecord::Base.connection.query_cache_enabled
+ ActiveRecord::Base.connection.enable_query_cache!
+
+ status, headers, body = @app.call(env)
+ [status, headers, BodyProxy.new(old, body)]
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index d38588519b..bae2ded244 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -26,10 +26,12 @@ module ActiveRecord
load "active_record/railties/databases.rake"
end
- # When loading console, force ActiveRecord to be loaded to avoid cross
- # references when loading a constant for the first time.
- console do
- ActiveRecord::Base
+ # When loading console, force ActiveRecord::Base to be loaded
+ # to avoid cross references when loading a constant for the
+ # first time. Also, make it output to STDERR.
+ console do |sandbox|
+ require "active_record/railties/console_sandbox" if sandbox
+ ActiveRecord::Base.logger = Logger.new(STDERR)
end
initializer "active_record.initialize_timezone" do
diff --git a/activerecord/lib/active_record/railties/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb
new file mode 100644
index 0000000000..65a3d68619
--- /dev/null
+++ b/activerecord/lib/active_record/railties/console_sandbox.rb
@@ -0,0 +1,6 @@
+ActiveRecord::Base.connection.increment_open_transactions
+ActiveRecord::Base.connection.begin_db_transaction
+at_exit do
+ ActiveRecord::Base.connection.rollback_db_transaction
+ ActiveRecord::Base.connection.decrement_open_transactions
+end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 0667be7d23..2c20dd997f 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index c6c1d1dad5..a2155d1dd1 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -31,6 +31,6 @@ module ActiveRecord
def test_table_exists_wrong_schema
assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
end
- end if current_adapter?(:MysqlAdapter)
+ end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
new file mode 100644
index 0000000000..858d1da2dd
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -0,0 +1,36 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class Mysql2SchemaTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ db = Post.connection_pool.spec.config[:database]
+ table = Post.table_name
+ @db_name = db
+
+ @omgpost = Class.new(Post) do
+ set_table_name "#{db}.#{table}"
+ def self.name; 'Post'; end
+ end
+ end
+
+ def test_schema
+ assert @omgpost.find(:first)
+ end
+
+ def test_table_exists?
+ name = @omgpost.table_name
+ assert @connection.table_exists?(name), "#{name} table should exist"
+ end
+
+ def test_table_exists_wrong_schema
+ assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/identity_map/middleware_test.rb b/activerecord/test/cases/identity_map/middleware_test.rb
new file mode 100644
index 0000000000..60dcad4586
--- /dev/null
+++ b/activerecord/test/cases/identity_map/middleware_test.rb
@@ -0,0 +1,71 @@
+require "cases/helper"
+
+module ActiveRecord
+ module IdentityMap
+ class MiddlewareTest < ActiveRecord::TestCase
+ def setup
+ super
+ @enabled = IdentityMap.enabled
+ IdentityMap.enabled = false
+ end
+
+ def teardown
+ super
+ IdentityMap.enabled = @enabled
+ IdentityMap.clear
+ end
+
+ def test_delegates
+ called = false
+ mw = Middleware.new lambda { |env|
+ called = true
+ }
+ mw.call({})
+ assert called, 'middleware delegated'
+ end
+
+ def test_im_enabled_during_delegation
+ mw = Middleware.new lambda { |env|
+ assert IdentityMap.enabled?, 'identity map should be enabled'
+ }
+ mw.call({})
+ end
+
+ class Enum < Struct.new(:iter)
+ def each(&b)
+ iter.call(&b)
+ end
+ end
+
+ def test_im_enabled_during_body_each
+ mw = Middleware.new lambda { |env|
+ [200, {}, Enum.new(lambda { |&b|
+ assert IdentityMap.enabled?, 'identity map should be enabled'
+ b.call "hello"
+ })]
+ }
+ body = mw.call({}).last
+ body.each { |x| assert_equal 'hello', x }
+ end
+
+ def test_im_disabled_after_body_close
+ mw = Middleware.new lambda { |env| [200, {}, []] }
+ body = mw.call({}).last
+ assert IdentityMap.enabled?, 'identity map should be enabled'
+ body.close
+ assert !IdentityMap.enabled?, 'identity map should be disabled'
+ end
+
+ def test_im_cleared_after_body_close
+ mw = Middleware.new lambda { |env| [200, {}, []] }
+ body = mw.call({}).last
+
+ IdentityMap.repository['hello'] = 'world'
+ assert !IdentityMap.repository.empty?, 'repo should not be empty'
+
+ body.close
+ assert IdentityMap.repository.empty?, 'repo should be empty'
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
index 2238529f0f..649715fbb5 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+
require 'models/developer'
require 'models/project'
require 'models/company'
@@ -382,15 +383,6 @@ class IdentityMapTest < ActiveRecord::TestCase
assert_not_nil post.title
end
- def test_log
- log = StringIO.new
- ActiveRecord::Base.logger = Logger.new(log)
- ActiveRecord::Base.logger.level = Logger::DEBUG
- Post.find 1
- Post.find 1
- assert_match(/Post with ID = 1 loaded from Identity Map/, log.string)
- end
-
# Currently AR is not allowing changing primary key (see Persistence#update)
# So we ignore it. If this changes, this test needs to be uncommented.
# def test_updating_of_pkey
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 5f55299065..c6c6079490 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -1,11 +1,14 @@
require "cases/helper"
require "models/developer"
+require "models/post"
require "active_support/log_subscriber/test_helper"
class LogSubscriberTest < ActiveRecord::TestCase
include ActiveSupport::LogSubscriber::TestHelper
include ActiveSupport::BufferedLogger::Severity
+ fixtures :posts
+
def setup
@old_logger = ActiveRecord::Base.logger
@using_identity_map = ActiveRecord::IdentityMap.enabled?
@@ -91,4 +94,13 @@ class LogSubscriberTest < ActiveRecord::TestCase
def test_initializes_runtime
Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
end
+
+ def test_log
+ ActiveRecord::IdentityMap.use do
+ Post.find 1
+ Post.find 1
+ end
+ wait
+ assert_match(/From Identity Map/, @logger.logged(:debug).last)
+ end
end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 2c051bff84..fbbae99e8b 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -5,7 +5,63 @@ require 'models/keyboard'
require 'models/task'
require 'models/person'
+
+module MassAssignmentTestHelpers
+ def setup
+ # another AR test modifies the columns which causes issues with create calls
+ TightPerson.reset_column_information
+ LoosePerson.reset_column_information
+ end
+
+ def attributes_hash
+ {
+ :id => 5,
+ :first_name => 'Josh',
+ :gender => 'm',
+ :comments => 'rides a sweet bike'
+ }
+ end
+
+ def assert_default_attributes(person, create = false)
+ unless create
+ assert_nil person.id
+ else
+ assert !!person.id
+ end
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_nil person.comments
+ end
+
+ def assert_admin_attributes(person, create = false)
+ unless create
+ assert_nil person.id
+ else
+ assert !!person.id
+ end
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_equal 'rides a sweet bike', person.comments
+ end
+
+ def assert_all_attributes(person)
+ assert_equal 5, person.id
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_equal 'rides a sweet bike', person.comments
+ end
+end
+
+module MassAssignmentRelationTestHelpers
+ def setup
+ super
+ @person = LoosePerson.create(attributes_hash)
+ end
+end
+
+
class MassAssignmentSecurityTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
def test_customized_primary_key_remains_protected
subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
@@ -35,60 +91,114 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
p = LoosePerson.new
p.assign_attributes(attributes_hash)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal nil, p.comments
+ assert_default_attributes(p)
end
def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used
p = LoosePerson.new
p.assign_attributes(attributes_hash, :without_protection => true)
- assert_equal 5, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal 'rides a sweet bike', p.comments
+ assert_all_attributes(p)
end
def test_assign_attributes_with_default_scope_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :default)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal nil, p.comments
+ assert_default_attributes(p)
end
def test_assign_attributes_with_admin_scope_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :admin)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal 'rides a sweet bike', p.comments
+ assert_admin_attributes(p)
end
def test_assign_attributes_with_default_scope_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :default)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal nil, p.comments
+ assert_default_attributes(p)
end
def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :admin)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal 'rides a sweet bike', p.comments
+ assert_admin_attributes(p)
+ end
+
+ def test_new_with_attr_accessible_attributes
+ p = TightPerson.new(attributes_hash)
+
+ assert_default_attributes(p)
+ end
+
+ def test_new_with_attr_protected_attributes
+ p = LoosePerson.new(attributes_hash)
+
+ assert_default_attributes(p)
+ end
+
+ def test_create_with_attr_accessible_attributes
+ p = TightPerson.create(attributes_hash)
+
+ assert_default_attributes(p, true)
+ end
+
+ def test_create_with_attr_protected_attributes
+ p = LoosePerson.create(attributes_hash)
+
+ assert_default_attributes(p, true)
+ end
+
+ def test_new_with_admin_scope_with_attr_accessible_attributes
+ p = TightPerson.new(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p)
+ end
+
+ def test_new_with_admin_scope_with_attr_protected_attributes
+ p = LoosePerson.new(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p)
+ end
+
+ def test_create_with_admin_scope_with_attr_accessible_attributes
+ p = TightPerson.create(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p, true)
+ end
+
+ def test_create_with_admin_scope_with_attr_protected_attributes
+ p = LoosePerson.create(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p, true)
+ end
+
+ def test_new_with_without_protection_with_attr_accessible_attributes
+ p = TightPerson.new(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
+ def test_new_with_without_protection_with_attr_protected_attributes
+ p = LoosePerson.new(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
+ def test_create_with_without_protection_with_attr_accessible_attributes
+ p = TightPerson.create(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
+ def test_create_with_without_protection_with_attr_protected_attributes
+ p = LoosePerson.create(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
end
def test_protection_against_class_attribute_writers
@@ -101,14 +211,268 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
end
- private
+end
- def attributes_hash
- {
- :id => 5,
- :first_name => 'Josh',
- :gender => 'm',
- :comments => 'rides a sweet bike'
- }
+
+class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+ include MassAssignmentRelationTestHelpers
+
+ # build
+
+ def test_has_one_build_with_attr_protected_attributes
+ best_friend = @person.build_best_friend(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_attr_accessible_attributes
+ best_friend = @person.build_best_friend(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_without_protection
+ best_friend = @person.build_best_friend(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create
+
+ def test_has_one_create_with_attr_protected_attributes
+ best_friend = @person.create_best_friend(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
end
-end \ No newline at end of file
+
+ def test_has_one_create_without_protection
+ best_friend = @person.create_best_friend(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create!
+
+ def test_has_one_create_with_bang_with_attr_protected_attributes
+ best_friend = @person.create_best_friend!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_without_protection
+ best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+end
+
+
+class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+ include MassAssignmentRelationTestHelpers
+
+ # build
+
+ def test_has_one_build_with_attr_protected_attributes
+ best_friend = @person.build_best_friend_of(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_attr_accessible_attributes
+ best_friend = @person.build_best_friend_of(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_without_protection
+ best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create
+
+ def test_has_one_create_with_attr_protected_attributes
+ best_friend = @person.create_best_friend_of(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend_of(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_without_protection
+ best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create!
+
+ def test_has_one_create_with_bang_with_attr_protected_attributes
+ best_friend = @person.create_best_friend!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_without_protection
+ best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+end
+
+
+class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+ include MassAssignmentRelationTestHelpers
+
+ # build
+
+ def test_has_one_build_with_attr_protected_attributes
+ best_friend = @person.best_friends.build(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_attr_accessible_attributes
+ best_friend = @person.best_friends.build(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_without_protection
+ best_friend = @person.best_friends.build(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create
+
+ def test_has_one_create_with_attr_protected_attributes
+ best_friend = @person.best_friends.create(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_attr_accessible_attributes
+ best_friend = @person.best_friends.create(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_without_protection
+ best_friend = @person.best_friends.create(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create!
+
+ def test_has_one_create_with_bang_with_attr_protected_attributes
+ best_friend = @person.best_friends.create!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_attr_accessible_attributes
+ best_friend = @person.best_friends.create!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_without_protection
+ best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 287f7e255b..a61180cfaf 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -10,6 +10,70 @@ class QueryCacheTest < ActiveRecord::TestCase
def setup
Task.connection.clear_query_cache
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
+ def test_middleware_delegates
+ called = false
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ called = true
+ }
+ mw.call({})
+ assert called, 'middleware should delegate'
+ end
+
+ def test_middleware_caches
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ Task.find 1
+ Task.find 1
+ assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ }
+ mw.call({})
+ end
+
+ def test_cache_enabled_during_call
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
+
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
+ }
+ mw.call({})
+ end
+
+ def test_cache_on_during_body_write
+ streaming = Class.new do
+ def each
+ yield ActiveRecord::Base.connection.query_cache_enabled
+ end
+ end
+
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ [200, {}, streaming.new]
+ }
+ body = mw.call({}).last
+ body.each { |x| assert x, 'cache should be on' }
+ body.close
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
+ end
+
+ def test_cache_off_after_close
+ mw = ActiveRecord::QueryCache.new lambda { |env| }
+ body = mw.call({}).last
+
+ assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
+ body.close
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
+ end
+
+ def test_cache_clear_after_close
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ Post.find(:first)
+ }
+ body = mw.call({}).last
+
+ assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
+ body.close
+ assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
end
def test_find_queries
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 2ed676fe69..864b3d4846 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -355,6 +355,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 50000, wheres[:salary]
end
+ def test_default_scope_with_module_includes
+ wheres = ModuleIncludedPoorDeveloperCalledJamis.scoped.where_values_hash
+ assert_equal "Jamis", wheres[:name]
+ assert_equal 50000, wheres[:salary]
+ end
+
def test_default_scope_with_multiple_calls
wheres = MultiplePoorDeveloperCalledJamis.scoped.where_values_hash
assert_equal "Jamis", wheres[:name]
@@ -456,18 +462,4 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
end
-
- def test_multiple_default_scope_calls_are_deprecated
- klass = Class.new(ActiveRecord::Base)
-
- assert_not_deprecated do
- klass.send(:default_scope, :foo => :bar)
- end
-
- assert_deprecated do
- klass.send(:default_scope, :foo => :bar)
- end
-
- assert_equal 2, klass.default_scopes.length
- end
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 10701dd6fd..152f804e16 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -129,28 +129,40 @@ end
class DeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
+
default_scope where(:name => 'Jamis')
scope :poor, where('salary < 150000')
end
class PoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
+
default_scope where(:name => 'Jamis', :salary => 50000)
end
class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis
self.table_name = 'developers'
- ActiveSupport::Deprecation.silence do
- default_scope where(:salary => 50000)
- end
+ default_scope where(:salary => 50000)
end
class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
+
default_scope where(:name => 'Jamis')
+ default_scope where(:salary => 50000)
+end
- ActiveSupport::Deprecation.silence do
- default_scope where(:salary => 50000)
- end
+module SalaryDefaultScope
+ extend ActiveSupport::Concern
+
+ included { default_scope where(:salary => 50000) }
end
+
+class ModuleIncludedPoorDeveloperCalledJamis < DeveloperCalledJamis
+ self.table_name = 'developers'
+
+ include SalaryDefaultScope
+end
+
+
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 9c4794902d..a58c9bf572 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -1,6 +1,6 @@
class Person < ActiveRecord::Base
has_many :readers
- has_one :reader
+ has_one :reader
has_many :posts, :through => :readers
has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null'
@@ -8,23 +8,23 @@ class Person < ActiveRecord::Base
has_many :references
has_many :bad_references
has_many :fixed_bad_references, :conditions => { :favourite => true }, :class_name => 'BadReference'
- has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
+ has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id'
has_many :jobs, :through => :references
- has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy
+ has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy
has_many :jobs_with_dependent_delete_all, :source => :job, :through => :references, :dependent => :delete_all
- has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify
+ has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify
belongs_to :primary_contact, :class_name => 'Person'
has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id'
has_many :agents_of_agents, :through => :agents, :source => :agents
belongs_to :number1_fan, :class_name => 'Person'
- has_many :agents_posts, :through => :agents, :source => :posts
+ has_many :agents_posts, :through => :agents, :source => :posts
has_many :agents_posts_authors, :through => :agents_posts, :source => :author
- scope :males, :conditions => { :gender => 'M' }
+ scope :males, :conditions => { :gender => 'M' }
scope :females, :conditions => { :gender => 'F' }
end
@@ -56,14 +56,25 @@ class LoosePerson < ActiveRecord::Base
attr_protected :comments
attr_protected :as => :admin
+
+ has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
+ belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id
+
+ has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
end
class LooseDescendant < LoosePerson; end
class TightPerson < ActiveRecord::Base
self.table_name = 'people'
+
attr_accessible :first_name, :gender
attr_accessible :first_name, :gender, :comments, :as => :admin
+
+ has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id
+ belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id
+
+ has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id
end
class TightDescendant < TightPerson; end \ No newline at end of file
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index ceadb05644..9479242e4f 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -438,6 +438,8 @@ ActiveRecord::Schema.define do
t.references :number1_fan
t.integer :lock_version, :null => false, :default => 0
t.string :comments
+ t.references :best_friend
+ t.references :best_friend_of
t.timestamps
end