aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG4
-rw-r--r--actionpack/actionpack.gemspec1
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb68
-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.rb86
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb8
-rw-r--r--activerecord/lib/active_record/identity_map.rb15
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb9
-rw-r--r--activerecord/lib/active_record/query_cache.rb27
-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.rb53
-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
-rw-r--r--activeresource/examples/performance.rb70
-rw-r--r--activeresource/lib/active_resource/base.rb39
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb10
-rw-r--r--railties/lib/rails/generators/app_base.rb11
24 files changed, 767 insertions, 203 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index f692b169df..184b2af018 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -8,7 +8,9 @@
* Implicit actions named not_implemented can be rendered [Santiago Pastorino]
-* Wildcard route will always matching the optional format segment by default. For example if you have this route:
+* Wildcard route will always matching the optional format segment by default. [Prem Sichanugrist]
+
+ For example if you have this route:
map '*pages' => 'pages#show'
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 0d667a76a7..a2570587ce 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
s.add_dependency('rack', '~> 1.2.1')
s.add_dependency('rack-test', '~> 0.5.7')
s.add_dependency('rack-mount', '~> 0.7.2')
+ s.add_dependency('sprockets', '~> 2.0.0.beta.1')
s.add_dependency('tzinfo', '~> 0.3.23')
s.add_dependency('erubis', '~> 2.7.0')
end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 0dd847f967..1d27c3aa51 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -2,17 +2,16 @@ require 'active_support/core_ext/file/path'
require 'rack/chunked'
module ActionController #:nodoc:
- # Allow views to be streamed back to the client as they are rendered.
+ # Allows views to be streamed back to the client as they are rendered.
#
# The default way Rails renders views is by first rendering the template
- # and then the layout. The first chunk of response is sent to the client
- # just after the whole template is rendered, all queries are made and the
- # layout is processed.
+ # and then the layout. The response is sent to the client after the whole
+ # template is rendered, all queries are made, and the layout is processed.
#
# Streaming inverts the rendering flow by rendering the layout first and
# streaming each part of the layout as they are processed. This allows the
- # header of the html (which is usually in the layout) to be streamed back
- # to client very quickly, allowing javascripts and stylesheets to be loaded
+ # header of the HTML (which is usually in the layout) to be streamed back
+ # to client very quickly, allowing JavaScripts and stylesheets to be loaded
# earlier than usual.
#
# This approach was introduced in Rails 3.1 and is still improving. Several
@@ -20,13 +19,13 @@ module ActionController #:nodoc:
# Those points are going to be addressed soon.
#
# In order to use streaming, you will need to use a Ruby version that
- # supports Fibers (Fibers are supported since version 1.9.2 of the main
+ # supports fibers (fibers are supported since version 1.9.2 of the main
# Ruby implementation).
#
# == Examples
#
# Streaming can be added to a controller easily, all you need to do is
- # call stream at the controller class:
+ # call +stream+ in the controller class:
#
# class PostsController
# stream
@@ -42,19 +41,19 @@ module ActionController #:nodoc:
#
# class PostsController
# def index
- # @post = Post.scoped
+ # @posts = Post.scoped
# render :stream => true
# end
# end
#
# == When to use streaming
#
- # Streaming may be considering an overkill for common actions like
- # new or edit. The real benefit of streaming is on expensive actions
- # that, for example, does a lot of queries on the database.
+ # Streaming may be considered to be overkill for lightweight actions like
+ # +new+ or +edit+. The real benefit of streaming is on expensive actions
+ # that, for example, do a lot of queries on the database.
#
# In such actions, you want to delay queries execution as much as you can.
- # For example, imagine the following dashboard action:
+ # For example, imagine the following +dashboard+ action:
#
# def dashboard
# @posts = Post.all
@@ -63,10 +62,10 @@ module ActionController #:nodoc:
# end
#
# Most of the queries here are happening in the controller. In order to benefit
- # most of streaming, you would want to rewrite it as:
+ # from streaming you would want to rewrite it as:
#
# def dashboard
- # # Allow lazily execution of the query
+ # # Allow lazy execution of the queries
# @posts = Post.scoped
# @pages = Page.scoped
# @articles = Article.scoped
@@ -75,12 +74,15 @@ module ActionController #:nodoc:
#
# == Communication between layout and template
#
- # When streaming, the layout is rendered first than the template.
- # This means that, if your application currently rely on variables set
- # in the template to be used in the layout, they won't work once you
- # move to streaming. The proper way to communicate between layout and
- # template, regardless if you use streaming or not, is by using
- # +content_for+, +provide+ and +yield+.
+ # When streaming, rendering happens top-down instead of inside-out.
+ # Rails starts with the layout, and the template is rendered later,
+ # when its +yield+ is reached.
+ #
+ # This means that, if your application currently relies on instance
+ # variables set in the template to be used in the layout, they won't
+ # work once you move to streaming. The proper way to communicate
+ # between layout and template, regardless of whether you use streaming
+ # or not, is by using +content_for+, +provide+ and +yield+.
#
# Take a simple example where the layout expects the template to tell
# which title to use:
@@ -121,16 +123,16 @@ module ActionController #:nodoc:
# and you want to use streaming, you would have to render the whole template
# (and eventually trigger all queries) before streaming the title and all
# assets, which kills the purpose of streaming. For this reason Rails 3.1
- # introduces a helper called +provide+ that does the same as +content_for+
+ # introduces a new helper called +provide+ that does the same as +content_for+
# but tells the layout to stop searching for other entries and continue rendering.
#
- # For instance, the template below, using +provide+:
+ # For instance, the template above using +provide+ would be:
#
# <%= provide :title, "Main" %>
# Hello
# <%= content_for :title, " page" %>
#
- # Has as final result:
+ # Giving:
#
# <html>
# <head><title>Main</title></head>
@@ -138,26 +140,26 @@ module ActionController #:nodoc:
# </html>
#
# That said, when streaming, you need to properly check your templates
- # and chose when to use +provide+ and +content_for+.
+ # and choose when to use +provide+ and +content_for+.
#
# == Headers, cookies, session and flash
#
# When streaming, the HTTP headers are sent to the client right before
# it renders the first line. This means that, modifying headers, cookies,
- # session or flash after the template start rendering will not propagate
+ # session or flash after the template starts rendering will not propagate
# to the client.
#
- # If you try to modify cookies, session or flash, a ClosedError will be
- # raised, showing those objects are closed for modification.
+ # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+
+ # will be raised, showing those objects are closed for modification.
#
# == Middlewares
#
# Middlewares that need to manipulate the body won't work with streaming.
# You should disable those middlewares whenever streaming in development
- # or production. For instance, Rack::Bug won't work when streaming as it
+ # or production. For instance, +Rack::Bug+ won't work when streaming as it
# needs to inject contents in the HTML body.
#
- # Also Rack::Cache won't work with streaming as it does not support
+ # Also +Rack::Cache+ won't work with streaming as it does not support
# streaming bodies yet. So, whenever streaming, Cache-Control is automatically
# set to "no-cache".
#
@@ -193,10 +195,10 @@ module ActionController #:nodoc:
#
# unicorn_rails --config-file unicorn.config.rb
#
- # You may also want to configure other parameters like :tcp_nodelay. Please
- # check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
+ # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
+ # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
#
- # If you are using unicorn with nginx, you may need to tweak nginx.
+ # If you are using Unicorn with Nginx, you may need to tweak Nginx.
# Streaming should work out of the box on Rainbows.
#
# ==== Passenger
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..1919ceb158 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -475,10 +475,19 @@ module ActiveRecord #:nodoc:
# 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
@@ -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:
@@ -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
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/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index d3a054c29b..b6f838e49c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -412,7 +412,7 @@ module ActiveRecord
def tables(name = nil)
tables = []
- execute("SHOW TABLES", name).each do |field|
+ execute("SHOW TABLES", 'SCHEMA').each do |field|
tables << field.first
end
tables
@@ -426,7 +426,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 +444,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")
}
@@ -546,7 +546,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/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index 95a8e5cff7..4ad0b0d205 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
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..929998eb85 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -27,10 +27,31 @@ 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
+ 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/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..b2e40c6b22 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -10,6 +10,59 @@ 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_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
diff --git a/activeresource/examples/performance.rb b/activeresource/examples/performance.rb
new file mode 100644
index 0000000000..e4df7a38a4
--- /dev/null
+++ b/activeresource/examples/performance.rb
@@ -0,0 +1,70 @@
+require 'rubygems'
+require 'active_resource'
+require 'benchmark'
+
+TIMES = (ENV['N'] || 10_000).to_i
+
+# deep nested resource
+attrs = {
+ :id => 1,
+ :name => 'Luis',
+ :age => 21,
+ :friends => [
+ {
+ :name => 'JK',
+ :age => 24,
+ :colors => ['red', 'green', 'blue'],
+ :brothers => [
+ {
+ :name => 'Mateo',
+ :age => 35,
+ :children => [{ :name => 'Edith', :age => 5 }, { :name => 'Martha', :age => 4 }]
+ },
+ {
+ :name => 'Felipe',
+ :age => 33,
+ :children => [{ :name => 'Bryan', :age => 1 }, { :name => 'Luke', :age => 0 }]
+ }
+ ]
+ },
+ {
+ :name => 'Eduardo',
+ :age => 20,
+ :colors => [],
+ :brothers => [
+ {
+ :name => 'Sebas',
+ :age => 23,
+ :children => [{ :name => 'Andres', :age => 0 }, { :name => 'Jorge', :age => 2 }]
+ },
+ {
+ :name => 'Elsa',
+ :age => 19,
+ :children => [{ :name => 'Natacha', :age => 1 }]
+ },
+ {
+ :name => 'Milena',
+ :age => 16,
+ :children => []
+ }
+ ]
+ }
+ ]
+}
+
+class Customer < ActiveResource::Base
+ self.site = "http://37s.sunrise.i:3000"
+end
+
+module Nested
+ class Customer < ActiveResource::Base
+ self.site = "http://37s.sunrise.i:3000"
+ end
+end
+
+Benchmark.bm(40) do |x|
+ x.report('Model.new (instantiation)') { TIMES.times { Customer.new } }
+ x.report('Nested::Model.new (instantiation)') { TIMES.times { Nested::Customer.new } }
+ x.report('Model.new (setting attributes)') { TIMES.times { Customer.new attrs } }
+ x.report('Nested::Model.new (setting attributes)') { TIMES.times { Nested::Customer.new attrs } }
+end
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 160763779e..7f2a844723 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -1239,9 +1239,10 @@ module ActiveResource
@attributes[key.to_s] =
case value
when Array
- resource = find_or_create_resource_for_collection(key)
+ resource = nil
value.map do |attrs|
if attrs.is_a?(Hash)
+ resource ||= find_or_create_resource_for_collection(key)
resource.new(attrs)
else
attrs.duplicable? ? attrs.dup : attrs
@@ -1251,7 +1252,7 @@ module ActiveResource
resource = find_or_create_resource_for(key)
resource.new(value)
else
- value.dup rescue value
+ value.duplicable? ? value.dup : value
end
end
self
@@ -1367,36 +1368,44 @@ module ActiveResource
end
# Tries to find a resource in a non empty list of nested modules
- # Raises a NameError if it was not found in any of the given nested modules
- def find_resource_in_modules(resource_name, module_names)
+ # if it fails, then the resource is created
+ def find_or_create_resource_in_modules(resource_name, module_names)
receiver = Object
namespaces = module_names[0, module_names.size-1].map do |module_name|
receiver = receiver.const_get(module_name)
end
const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
- return namespace.const_get(*const_args)
+ namespace.const_get(*const_args)
else
- raise NameError
+ create_resource_for(resource_name)
end
end
# Tries to find a resource for a given name; if it fails, then the resource is created
def find_or_create_resource_for(name)
resource_name = name.to_s.camelize
- ancestors = self.class.name.split("::")
- if ancestors.size > 1
- find_resource_in_modules(resource_name, ancestors)
- else
- self.class.const_get(resource_name)
- end
- rescue NameError
+
const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
if self.class.const_defined?(*const_args)
- resource = self.class.const_get(*const_args)
+ self.class.const_get(*const_args)
else
- resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
+ ancestors = self.class.name.split("::")
+ if ancestors.size > 1
+ find_or_create_resource_in_modules(resource_name, ancestors)
+ else
+ if Object.const_defined?(*const_args)
+ Object.const_get(*const_args)
+ else
+ create_resource_for(resource_name)
+ end
+ end
end
+ end
+
+ # Create and return a class definition for a resource inside the current resource
+ def create_resource_for(resource_name)
+ resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
resource.prefix = self.class.prefix
resource.site = self.class.site
resource
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index bfc2024529..05da50e150 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -45,17 +45,17 @@ module ActiveSupport
# post :delete, :id => ...
# end
def assert_difference(expression, difference = 1, message = nil, &block)
- b = block.send(:binding)
- exps = Array.wrap(expression)
- before = exps.map { |e| e.respond_to?(:call) ? e.call : eval(e, b) }
+ exps = Array.wrap(expression).map { |e|
+ e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
+ }
+ before = exps.map { |e| e.call }
yield
exps.each_with_index do |e, i|
error = "#{e.inspect} didn't change by #{difference}"
error = "#{message}.\n#{error}" if message
- actual = e.respond_to?(:call) ? e.call : eval(e, b)
- assert_equal(before[i] + difference, actual, error)
+ assert_equal(before[i] + difference, e.call, error)
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 324199e71c..689ef921e1 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -132,14 +132,12 @@ module Rails
gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
gem 'arel', :git => 'git://github.com/rails/arel.git'
gem 'rack', :git => 'git://github.com/rack/rack.git'
- gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'arel', :git => 'git://github.com/rails/arel.git'
gem 'rack', :git => 'git://github.com/rack/rack.git'
- gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
else
<<-GEMFILE.strip_heredoc
@@ -149,7 +147,6 @@ module Rails
# gem 'rails', :git => 'git://github.com/rails/rails.git'
# gem 'arel', :git => 'git://github.com/rails/arel.git'
# gem 'rack', :git => 'git://github.com/rack/rack.git'
- # gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
end
end
@@ -167,7 +164,7 @@ module Rails
else options[:database]
end
end
-
+
def gem_for_ruby_debugger
if RUBY_VERSION < "1.9.2"
"gem 'ruby-debug'"
@@ -175,7 +172,7 @@ module Rails
"gem 'ruby-debug19', :require => 'ruby-debug'"
end
end
-
+
def gem_for_turn
unless RUBY_VERSION < "1.9.2"
<<-GEMFILE.strip_heredoc
@@ -200,7 +197,7 @@ module Rails
empty_directory(destination, config)
git_keep(destination)
end
-
+
def git_keep(destination)
create_file("#{destination}/.gitkeep") unless options[:skip_git]
end
@@ -216,4 +213,4 @@ module Rails
end
end
end
-end \ No newline at end of file
+end