aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--Gemfile7
-rw-r--r--README.rdoc4
-rwxr-xr-xRakefile2
-rw-r--r--actionpack/CHANGELOG2
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb9
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb6
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb2
-rw-r--r--actionpack/lib/action_view/base.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb8
-rw-r--r--actionpack/lib/action_view/lookup_context.rb2
-rw-r--r--actionpack/lib/action_view/path_set.rb73
-rw-r--r--actionpack/lib/action_view/template/resolver.rb50
-rw-r--r--actionpack/lib/sprockets/assets.rake31
-rw-r--r--actionpack/lib/sprockets/railtie.rb1
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb7
-rw-r--r--actionpack/test/controller/redirect_test.rb5
-rw-r--r--actionpack/test/controller/routing_test.rb108
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb183
-rw-r--r--actionpack/test/dispatch/routing_test.rb12
-rw-r--r--actionpack/test/template/compiled_templates_test.rb2
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb8
-rw-r--r--actionpack/test/template/render_test.rb2
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb13
-rw-r--r--activemodel/lib/active_model/observing.rb4
-rw-r--r--activemodel/test/cases/observing_test.rb12
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/associations/association.rb16
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb5
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb5
-rw-r--r--activerecord/lib/active_record/base.rb73
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb56
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb10
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb6
-rw-r--r--activerecord/lib/active_record/observer.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb29
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/test/cases/adapter_test.rb13
-rw-r--r--activerecord/test/cases/associations/extension_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb39
-rw-r--r--activerecord/test/cases/calculations_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb4
-rw-r--r--activerecord/test/cases/lifecycle_test.rb12
-rw-r--r--activerecord/test/cases/method_scoping_test.rb8
-rw-r--r--activerecord/test/cases/primary_keys_test.rb2
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/relations_test.rb38
-rw-r--r--activerecord/test/models/developer.rb9
-rw-r--r--activesupport/CHANGELOG6
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/io.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb55
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/public_send.rb25
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb1
-rw-r--r--activesupport/lib/active_support/gzip.rb1
-rw-r--r--activesupport/lib/active_support/inflections.rb1
-rw-r--r--activesupport/lib/active_support/notifications.rb2
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb34
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb10
-rw-r--r--activesupport/test/core_ext/io_test.rb23
-rw-r--r--activesupport/test/core_ext/module_test.rb56
-rw-r--r--activesupport/test/core_ext/object/public_send_test.rb117
-rw-r--r--activesupport/test/notifications_test.rb1
-rw-r--r--railties/guides/rails_guides/generator.rb48
-rw-r--r--railties/guides/rails_guides/textile_extensions.rb25
-rw-r--r--railties/guides/source/active_support_core_extensions.textile28
-rw-r--r--railties/guides/source/i18n.textile2
-rw-r--r--railties/lib/rails/application/configuration.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt12
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt (renamed from railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt)0
-rw-r--r--railties/lib/rails/railtie.rb1
-rw-r--r--railties/lib/rails/tasks/documentation.rake2
-rw-r--r--railties/test/application/assets_test.rb22
-rw-r--r--railties/test/application/middleware/sendfile_test.rb3
-rw-r--r--tasks/release.rb14
99 files changed, 1168 insertions, 450 deletions
diff --git a/.travis.yml b/.travis.yml
index 596611ecf4..6130b69631 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,7 @@ script: 'ci/travis.rb'
rvm:
- 1.8.7
- 1.9.2
+ - 1.9.3
env:
- "GEM=railties"
- "GEM=ap,am,amo,ares,as"
@@ -11,4 +12,5 @@ env:
notifications:
email: false
irc:
- - "irc.freenode.org#rails-contrib" \ No newline at end of file
+ - "irc.freenode.org#rails-contrib"
+bundler_args: --path vendor/bundle
diff --git a/Gemfile b/Gemfile
index f6caa1ee65..e11514d5e6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,7 +11,12 @@ gem "jquery-rails"
# it being automatically loaded by sprockets
gem "uglifier", ">= 1.0.0", :require => false
-gem "rake", ">= 0.8.7"
+# Temp fix until rake 0.9.3 is out
+if RUBY_VERSION >= "1.9.3"
+ gem "rake", "0.9.3.beta.1"
+else
+ gem "rake", ">= 0.8.7"
+end
gem "mocha", ">= 0.9.8"
group :doc do
diff --git a/README.rdoc b/README.rdoc
index 5d1e3f5c5d..1e78799e83 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -18,7 +18,7 @@ you to present the data from database rows as objects and embellish these data o
with business logic methods. Although most Rails models are backed by a database, models
can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as
provided by the ActiveModel module. You can read more about Active Record in its
-{README}[link:blob/master/activerecord/README.rdoc].
+{README}[link:/rails/rails/blob/master/activerecord/README.rdoc].
The Controller layer is responsible for handling incoming HTTP requests and providing a
suitable response. Usually this means returning HTML, but Rails controllers can also
@@ -29,7 +29,7 @@ In Rails, the Controller and View layers are handled together by Action Pack.
These two layers are bundled in a single package due to their heavy interdependence.
This is unlike the relationship between Active Record and Action Pack which are
independent. Each of these packages can be used independently outside of Rails. You
-can read more about Action Pack in its {README}[link:blob/master/actionpack/README.rdoc].
+can read more about Action Pack in its {README}[link:/rails/rails/blob/master/actionpack/README.rdoc].
== Getting Started
diff --git a/Rakefile b/Rakefile
index d18dac0dbd..36007f6e91 100755
--- a/Rakefile
+++ b/Rakefile
@@ -74,7 +74,7 @@ RDoc::Task.new do |rdoc|
# since no autolinking happens there and RDoc displays the backslash
# otherwise.
rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" }
- rdoc_main.gsub!(%r{link:blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html")
+ rdoc_main.gsub!(%r{link:/rails/rails/blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html")
File.open(RDOC_MAIN, 'w') do |f|
f.write(rdoc_main)
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 3b323b3899..b1286d04cc 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -30,6 +30,8 @@
*Rails 3.1.0 (unreleased)*
+* x_sendfile_header now defaults to nil and config/environments/production.rb doesn't set a particular value for it. This allows servers to set it through X-Sendfile-Type. [Santiago Pastorino]
+
* The submit form helper does not generate an id "object_name_id" anymore. [fbrusatti]
* Make sure respond_with with :js tries to render a template in all cases [José Valim]
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index 6b7aae8c74..e8394447a7 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -1,3 +1,5 @@
+require 'action_view/base'
+
module AbstractController
module ViewPaths
extend ActiveSupport::Concern
@@ -63,7 +65,7 @@ module AbstractController
# the default view path. You may also provide a custom view path
# (see ActionView::PathSet for more information)
def append_view_path(path)
- self.view_paths = view_paths.dup + Array(path)
+ self._view_paths = view_paths + Array(path)
end
# Prepend a path to the list of view paths for this controller.
@@ -73,7 +75,7 @@ module AbstractController
# the default view path. You may also provide a custom view path
# (see ActionView::PathSet for more information)
def prepend_view_path(path)
- self.view_paths = Array(path) + view_paths.dup
+ self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
end
# A list of all of the default view paths for this controller.
@@ -87,8 +89,7 @@ module AbstractController
# * <tt>paths</tt> - If a PathSet is provided, use that;
# otherwise, process the parameter into a PathSet.
def view_paths=(paths)
- self._view_paths = ActionView::Base.process_view_paths(paths)
- self._view_paths.freeze
+ self._view_paths = ActionView::PathSet.new(Array.wrap(paths))
end
end
end
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 0785fe9679..5e077dd7bd 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -17,7 +17,7 @@ module ActionController #:nodoc:
protected
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
- # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile".
+ # config.action_dispatch.x_sendfile_header.
# Your server can also configure this for you by setting the X-Sendfile-Type header.
#
# Be careful to sanitize the path parameter if it is coming from a web
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 85d0f5f699..777a0ab343 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -58,8 +58,8 @@ module ActionController
def redirect_to(*args)
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
result = super
- payload[:status] = self.status
- payload[:location] = self.location
+ payload[:status] = response.status
+ payload[:location] = response.location
result
end
end
@@ -97,4 +97,4 @@ module ActionController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 2d8afc3a78..dfc4e3d72c 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -121,6 +121,8 @@ module ActionController
_set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
end
+ alias :wrap_parameters= :wrap_parameters
+
# Sets the default wrapper key or model which will be used to determine
# wrapper key and attribute names. Will be called automatically when the
# module is inherited.
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index f51cc3711b..fbda1f8442 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -4,7 +4,7 @@ require "rails"
module ActionDispatch
class Railtie < Rails::Railtie
config.action_dispatch = ActiveSupport::OrderedOptions.new
- config.action_dispatch.x_sendfile_header = ""
+ config.action_dispatch.x_sendfile_header = nil
config.action_dispatch.ip_spoofing_check = true
config.action_dispatch.show_exceptions = true
config.action_dispatch.best_standards_support = true
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 480144fe9d..de14113c51 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -140,7 +140,7 @@ module ActionDispatch
when String
options
when nil, Hash
- _routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys)
+ _routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys)
else
polymorphic_url(options)
end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 36a0a20066..43d67f2032 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/array/wrap'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
+require 'active_support/core_ext/module/deprecation'
module ActionView #:nodoc:
# = Action View Base
@@ -161,6 +162,7 @@ module ActionView #:nodoc:
value.is_a?(PathSet) ?
value.dup : ActionView::PathSet.new(Array.wrap(value))
end
+ deprecate :process_view_paths
def xss_safe? #:nodoc:
true
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 2bbe0c175f..9ed4611123 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -56,8 +56,8 @@ module ActionView
# form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae")
# # form with custom authenticity token
#
- def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
- html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
+ def form_tag(url_for_options = {}, options = {}, &block)
+ html_options = html_options_for_form(url_for_options, options)
if block_given?
form_tag_in_block(html_options, &block)
else
@@ -604,12 +604,12 @@ module ActionView
end
private
- def html_options_for_form(url_for_options, options, *parameters_for_url)
+ def html_options_for_form(url_for_options, options)
options.stringify_keys.tap do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
# The following URL is unescaped, this is just a hash of options, and it is the
# responsibility of the caller to escape all the values.
- html_options["action"] = url_for(url_for_options, *parameters_for_url)
+ html_options["action"] = url_for(url_for_options)
html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 560df15e82..9ec410ac2b 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -78,7 +78,7 @@ module ActionView
# Whenever setting view paths, makes a copy so we can manipulate then in
# instance objects as we wish.
def view_paths=(paths)
- @view_paths = ActionView::Base.process_view_paths(paths)
+ @view_paths = ActionView::PathSet.new(Array.wrap(paths))
end
def find(name, prefixes = [], partial = false, keys = [])
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index e0cb5d6a70..bbb1af8154 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -1,11 +1,55 @@
module ActionView #:nodoc:
# = Action View PathSet
- class PathSet < Array #:nodoc:
- %w(initialize << concat insert push unshift).each do |method|
+ class PathSet #:nodoc:
+ include Enumerable
+
+ attr_reader :paths
+
+ def initialize(paths = [])
+ @paths = typecast paths
+ end
+
+ def initialize_copy(other)
+ @paths = other.paths.dup
+ self
+ end
+
+ def [](i)
+ paths[i]
+ end
+
+ def to_ary
+ paths.dup
+ end
+
+ def include?(item)
+ paths.include? item
+ end
+
+ def pop
+ paths.pop
+ end
+
+ def size
+ paths.size
+ end
+
+ def each(&block)
+ paths.each(&block)
+ end
+
+ def compact
+ PathSet.new paths.compact
+ end
+
+ def +(array)
+ PathSet.new(paths + array)
+ end
+
+ %w(<< concat push insert unshift).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(*args)
- super
- typecast!
+ paths.#{method}(*typecast(args))
end
METHOD
end
@@ -17,7 +61,7 @@ module ActionView #:nodoc:
def find_all(path, prefixes = [], *args)
prefixes = [prefixes] if String === prefixes
prefixes.each do |prefix|
- each do |resolver|
+ paths.each do |resolver|
templates = resolver.find_all(path, prefix, *args)
return templates unless templates.empty?
end
@@ -25,17 +69,20 @@ module ActionView #:nodoc:
[]
end
- def exists?(*args)
- find_all(*args).any?
+ def exists?(path, prefixes, *args)
+ find_all(path, prefixes, *args).any?
end
- protected
+ private
- def typecast!
- each_with_index do |path, i|
- path = path.to_s if path.is_a?(Pathname)
- next unless path.is_a?(String)
- self[i] = OptimizedFileSystemResolver.new(path)
+ def typecast(paths)
+ paths.map do |path|
+ case path
+ when Pathname, String
+ OptimizedFileSystemResolver.new path.to_s
+ else
+ path
+ end
end
end
end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 5f7fe81bd5..7abaa07bc7 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -1,5 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
+require "active_support/core_ext/io"
require "action_view/template"
module ActionView
@@ -68,7 +69,7 @@ module ActionView
# before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
- locals = sort_locals(locals)
+ locals = locals.map { |x| x.to_s }.sort!
if key && caching?
@cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals)
@@ -97,18 +98,6 @@ module ActionView
t.virtual_path ||= (cached ||= build_path(*path_info))
end
end
-
- if :symbol.respond_to?("<=>")
- def sort_locals(locals) #:nodoc:
- locals.sort.freeze
- end
- else
- def sort_locals(locals) #:nodoc:
- locals = locals.map{ |l| l.to_s }
- locals.sort!
- locals.freeze
- end
- end
end
# An abstract class that implements a Resolver with path semantics.
@@ -130,20 +119,24 @@ module ActionView
def query(path, details, formats)
query = build_query(path, details)
- templates = []
- sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
- Dir[query].each do |p|
- next if File.directory?(p) || !sanitizer[p].include?(p)
+ # deals with case-insensitive file systems.
+ sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
- handler, format = extract_handler_and_format(p, formats)
- contents = File.open(p, "rb") { |io| io.read }
+ template_paths = Dir[query].reject { |filename|
+ File.directory?(filename) ||
+ !sanitizer[File.dirname(filename)].include?(filename)
+ }
- templates << Template.new(contents, File.expand_path(p), handler,
- :virtual_path => path.virtual, :format => format, :updated_at => mtime(p))
- end
+ template_paths.map { |template|
+ handler, format = extract_handler_and_format(template, formats)
+ contents = File.binread template
- templates
+ Template.new(contents, File.expand_path(template), handler,
+ :virtual_path => path.virtual,
+ :format => format,
+ :updated_at => mtime(template))
+ }
end
# Helper for building query glob string based on resolver's pattern.
@@ -235,15 +228,10 @@ module ActionView
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
def build_query(path, details)
exts = EXTENSIONS.map { |ext| details[ext] }
- query = File.join(@path, path)
-
- exts.each do |ext|
- query << "{"
- ext.compact.each { |e| query << ".#{e}," }
- query << "}"
- end
- query
+ File.join(@path, path) + exts.map { |ext|
+ "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
+ }.join
end
end
diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake
index 50278cffcd..4cf5caab91 100644
--- a/actionpack/lib/sprockets/assets.rake
+++ b/actionpack/lib/sprockets/assets.rake
@@ -1,22 +1,27 @@
namespace :assets do
- # Ensures the RAILS_GROUPS environment variable is set
- task :ensure_env do
- ENV["RAILS_GROUPS"] ||= "assets"
- end
-
desc "Compile all the assets named in config.assets.precompile"
- task :precompile => :ensure_env do
- Rake::Task["environment"].invoke
- Sprockets::Helpers::RailsHelper
+ task :precompile do
+ # We need to do this dance because RAILS_GROUPS is used
+ # too early in the boot process and changing here is already too late.
+ if ENV["RAILS_GROUPS"].to_s.empty? || ENV["RAILS_ENV"].to_s.empty?
+ ENV["RAILS_GROUPS"] ||= "assets"
+ ENV["RAILS_ENV"] ||= "production"
+ Kernel.exec $0, *ARGV
+ else
+ Rake::Task["environment"].invoke
+
+ # Ensure that action view is loaded and the appropriate sprockets hooks get executed
+ ActionView::Base
- assets = Rails.application.config.assets.precompile
- # Always perform caching so that asset_path appends the timestamps to file references.
- Rails.application.config.action_controller.perform_caching = true
- Rails.application.assets.precompile(*assets)
+ assets = Rails.application.config.assets.precompile
+ # Always perform caching so that asset_path appends the timestamps to file references.
+ Rails.application.config.action_controller.perform_caching = true
+ Rails.application.assets.precompile(*assets)
+ end
end
desc "Remove compiled assets"
- task :clean => :environment do
+ task :clean => [:environment, 'tmp:cache:clear'] do
assets = Rails.application.config.assets
public_asset_path = Rails.public_path + assets.prefix
rm_rf public_asset_path, :secure => true
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index c8438e6043..0a2c8c1ea3 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -20,6 +20,7 @@ module Sprockets
app.assets = Sprockets::Environment.new(app.root.to_s) do |env|
env.static_root = File.join(app.root.join('public'), config.assets.prefix)
env.logger = ::Rails.logger
+ env.version = ::Rails.env + "#{'-' + config.assets.version if config.assets.version.present?}"
if config.assets.cache_store != false
env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 7bef1e8d5d..e1914cf894 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -180,6 +180,13 @@ class ParamsWrapperTest < ActionController::TestCase
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
end
end
+
+ def test_assignment_alias
+ with_default_wrapper_options do
+ UsersController.wrap_parameters = { :format => [:foo] }
+ assert_equal({ :format => [:foo], :name => "user" }, UsersController._wrapper_options)
+ end
+ end
end
class NamespacedParamsWrapperTest < ActionController::TestCase
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 92d4a6d98b..79041055bd 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -4,6 +4,11 @@ class WorkshopsController < ActionController::Base
end
class RedirectController < ActionController::Base
+ # empty method not used anywhere to ensure methods like
+ # `status` and `location` aren't called on `redirect_to` calls
+ def status; render :text => 'called status'; end
+ def location; render :text => 'called location'; end
+
def simple_redirect
redirect_to :action => "hello_world"
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index b693fbec2b..5bf68decca 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1664,114 +1664,6 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert_raise(ActionController::RoutingError) { @routes.recognize_path('/none', :method => :get) }
end
- def test_generate
- assert_equal '/admin/users', url_for(@routes, { :use_route => 'admin_users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'admin/users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'admin/users', :action => 'index' })
- assert_equal '/admin/users', url_for(@routes, { :action => 'index' }, { :controller => 'admin/users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts' })
- assert_equal '/people', url_for(@routes, { :controller => '/people', :action => 'index' }, { :controller => 'admin/accounts' })
-
- assert_equal '/admin/posts', url_for(@routes, { :controller => 'admin/posts' })
- assert_equal '/admin/posts/new', url_for(@routes, { :controller => 'admin/posts', :action => 'new' })
-
- assert_equal '/blog/2009', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009 })
- assert_equal '/blog/2009/1', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1 })
- assert_equal '/blog/2009/1/1', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1 })
-
- assert_equal '/archive/2010', url_for(@routes, { :controller => 'archive', :action => 'index', :year => '2010' })
- assert_equal '/archive', url_for(@routes, { :controller => 'archive', :action => 'index' })
- assert_equal '/archive?year=january', url_for(@routes, { :controller => 'archive', :action => 'index', :year => 'january' })
-
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' })
- assert_equal '/people', url_for(@routes, { :action => 'index' }, { :controller => 'people' })
- assert_equal '/people', url_for(@routes, { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, {}, { :controller => 'people', :action => 'index' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/new', url_for(@routes, { :use_route => 'new_person' })
- assert_equal '/people/new', url_for(@routes, { :controller => 'people', :action => 'new' })
- assert_equal '/people/1', url_for(@routes, { :use_route => 'person', :id => '1' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1.xml', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => 1 })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => Model.new('1') })
- assert_equal '/people/1', url_for(@routes, { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' })
- assert_equal '/people/1', url_for(@routes, { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1', url_for(@routes, {}, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' })
- assert_equal '/people/1/edit', url_for(@routes, { :controller => 'people', :action => 'edit', :id => '1' })
- assert_equal '/people/1/edit.xml', url_for(@routes, { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' })
- assert_equal '/people/1/edit', url_for(@routes, { :use_route => 'edit_person', :id => '1' })
- assert_equal '/people/1?legacy=true', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1', :legacy => 'true' })
- assert_equal '/people?legacy=true', url_for(@routes, { :controller => 'people', :action => 'index', :legacy => 'true' })
-
- assert_equal '/id_default/2', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => '2' })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => '1' })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => 1 })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default' })
- assert_equal '/optional/bar', url_for(@routes, { :controller => 'posts', :action => 'index', :optional => 'bar' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts', :action => 'index' })
-
- assert_equal '/project', url_for(@routes, { :controller => 'project', :action => 'index' })
- assert_equal '/projects/1', url_for(@routes, { :controller => 'project', :action => 'index', :project_id => '1' })
- assert_equal '/projects/1', url_for(@routes, { :controller => 'project', :action => 'index'}, {:project_id => '1' })
- assert_raise(ActionController::RoutingError) { url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index' }) }
- assert_equal '/projects/1', url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' })
- assert_equal '/projects/1', url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index' }, { :project_id => '1' })
-
- assert_equal '/clients', url_for(@routes, { :controller => 'projects', :action => 'index' })
- assert_equal '/clients?project_id=1', url_for(@routes, { :controller => 'projects', :action => 'index', :project_id => '1' })
- assert_equal '/clients', url_for(@routes, { :controller => 'projects', :action => 'index' }, { :project_id => '1' })
- assert_equal '/clients', url_for(@routes, { :action => 'index' }, { :controller => 'projects', :action => 'index', :project_id => '1' })
-
- assert_equal '/comment/20', url_for(@routes, { :id => 20 }, { :controller => 'comments', :action => 'show' })
- assert_equal '/comment/20', url_for(@routes, { :controller => 'comments', :id => 20, :action => 'show' })
- assert_equal '/comments/boo', url_for(@routes, { :controller => 'comments', :action => 'boo' })
-
- assert_equal '/ws/posts/show/1', url_for(@routes, { :controller => 'posts', :action => 'show', :id => '1', :ws => true })
- assert_equal '/ws/posts', url_for(@routes, { :controller => 'posts', :action => 'index', :ws => true })
-
- assert_equal '/account', url_for(@routes, { :controller => 'account', :action => 'subscription' })
- assert_equal '/account/billing', url_for(@routes, { :controller => 'account', :action => 'billing' })
-
- assert_equal '/pages/1/notes/show/1', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/pages/1/notes/list', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'list' })
- assert_equal '/pages/1/notes', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'index' })
- assert_equal '/pages/1/notes', url_for(@routes, { :page_id => '1', :controller => 'notes' })
- assert_equal '/notes', url_for(@routes, { :page_id => nil, :controller => 'notes' })
- assert_equal '/notes', url_for(@routes, { :controller => 'notes' })
- assert_equal '/notes/print', url_for(@routes, { :controller => 'notes', :action => 'print' })
- assert_equal '/notes/print', url_for(@routes, {}, { :controller => 'notes', :action => 'print' })
-
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1', :foo => 'bar' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :action => 'index' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, {}, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/show/1', url_for(@routes, {}, { :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes', :id => '1' }, { :foo => 'bar' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/notes/list', url_for(@routes, { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' })
-
- assert_equal '/posts/ping', url_for(@routes, { :controller => 'posts', :action => 'ping' })
- assert_equal '/posts/show/1', url_for(@routes, { :controller => 'posts', :action => 'show', :id => '1' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts', :action => 'index' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' }, { :controller => 'posts', :action => 'index' })
- assert_equal '/posts/create', url_for(@routes, { :action => 'create' }, { :controller => 'posts' })
- assert_equal '/posts?foo=bar', url_for(@routes, { :controller => 'posts', :foo => 'bar' })
- assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', url_for(@routes, { :controller => 'posts', :foo => ['bar', 'baz'] })
- assert_equal '/posts?page=2', url_for(@routes, { :controller => 'posts', :page => 2 })
- assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', url_for(@routes, { :controller => 'posts', :q => { :foo => { :a => 'b'}} })
-
- assert_equal '/news.rss', url_for(@routes, { :controller => 'news', :action => 'index', :format => 'rss' })
-
-
- assert_raise(ActionController::RoutingError) { url_for(@routes, { :action => 'index' }) }
- end
-
def test_generate_extras
assert_equal ['/people', []], @routes.generate_extras(:controller => 'people')
assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :foo => 'bar')
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
new file mode 100644
index 0000000000..7b734ff0fb
--- /dev/null
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -0,0 +1,183 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'controller/fake_controllers'
+require 'active_support/core_ext/object/with_options'
+
+module RoutingTestHelpers
+ def url_for(set, options, recall = nil)
+ set.send(:url_for, options.merge(:only_path => true, :_path_segments => recall))
+ end
+end
+
+module ActionPack
+ class URLForIntegrationTest < ActiveSupport::TestCase
+ include RoutingTestHelpers
+
+ Model = Struct.new(:to_param)
+
+ Mapping = lambda {
+ namespace :admin do
+ resources :users, :posts
+ end
+
+ namespace 'api' do
+ root :to => 'users#index'
+ end
+
+ match '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
+ :constraints => {
+ :year => /(19|20)\d\d/,
+ :month => /[01]?\d/,
+ :day => /[0-3]?\d/
+ },
+ :day => nil,
+ :month => nil
+
+ match 'archive/:year', :controller => 'archive', :action => 'index',
+ :defaults => { :year => nil },
+ :constraints => { :year => /\d{4}/ },
+ :as => "blog"
+
+ resources :people
+ #match 'legacy/people' => "people#index", :legacy => "true"
+
+ match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ match 'id_default(/:id)' => "foo#id_default", :id => 1
+ match 'get_or_post' => "foo#get_or_post", :via => [:get, :post]
+ match 'optional/:optional' => "posts#index"
+ match 'projects/:project_id' => "project#index", :as => "project"
+ match 'clients' => "projects#index"
+
+ match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
+ :postalcode => /# Postcode format
+ \d{5} #Prefix
+ (-\d{4})? #Suffix
+ /x
+ }, :as => "geocode"
+
+ match 'news(.:format)' => "news#index"
+
+ match 'comment/:id(/:action)' => "comments#show"
+ match 'ws/:controller(/:action(/:id))', :ws => true
+ match 'account(/:action)' => "account#subscription"
+ match 'pages/:page_id/:controller(/:action(/:id))'
+ match ':controller/ping', :action => 'ping'
+ match ':controller(/:action(/:id))(.:format)'
+ root :to => "news#index"
+ }
+
+ def setup
+ @routes = ActionDispatch::Routing::RouteSet.new
+ @routes.draw(&Mapping)
+ end
+
+ [
+ ['/admin/users',[ { :use_route => 'admin_users' }]],
+ ['/admin/users',[ { :controller => 'admin/users' }]],
+ ['/admin/users',[ { :controller => 'admin/users', :action => 'index' }]],
+ ['/admin/users',[ { :action => 'index' }, { :controller => 'admin/users' }]],
+ ['/admin/users',[ { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts' }]],
+ ['/people',[ { :controller => '/people', :action => 'index' }, { :controller => 'admin/accounts' }]],
+
+ ['/admin/posts',[ { :controller => 'admin/posts' }]],
+ ['/admin/posts/new',[ { :controller => 'admin/posts', :action => 'new' }]],
+
+ ['/blog/2009',[ { :controller => 'posts', :action => 'show_date', :year => 2009 }]],
+ ['/blog/2009/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1 }]],
+ ['/blog/2009/1/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1 }]],
+
+ ['/archive/2010',[ { :controller => 'archive', :action => 'index', :year => '2010' }]],
+ ['/archive',[ { :controller => 'archive', :action => 'index' }]],
+ ['/archive?year=january',[ { :controller => 'archive', :action => 'index', :year => 'january' }]],
+
+ ['/people',[ { :controller => 'people', :action => 'index' }]],
+ ['/people',[ { :action => 'index' }, { :controller => 'people' }]],
+ ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ {}, { :controller => 'people', :action => 'index' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/new',[ { :use_route => 'new_person' }]],
+ ['/people/new',[ { :controller => 'people', :action => 'new' }]],
+ ['/people/1',[ { :use_route => 'person', :id => '1' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1.xml',[ { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => 1 }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => Model.new('1') }]],
+ ['/people/1',[ { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' }]],
+ ['/people/1',[ { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1',[ {}, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' }]],
+ ['/people/1/edit',[ { :controller => 'people', :action => 'edit', :id => '1' }]],
+ ['/people/1/edit.xml',[ { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' }]],
+ ['/people/1/edit',[ { :use_route => 'edit_person', :id => '1' }]],
+ ['/people/1?legacy=true',[ { :controller => 'people', :action => 'show', :id => '1', :legacy => 'true' }]],
+ ['/people?legacy=true',[ { :controller => 'people', :action => 'index', :legacy => 'true' }]],
+
+ ['/id_default/2',[ { :controller => 'foo', :action => 'id_default', :id => '2' }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => '1' }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => 1 }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default' }]],
+ ['/optional/bar',[ { :controller => 'posts', :action => 'index', :optional => 'bar' }]],
+ ['/posts',[ { :controller => 'posts', :action => 'index' }]],
+
+ ['/project',[ { :controller => 'project', :action => 'index' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index', :project_id => '1' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index'}, {:project_id => '1' }]],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' }]],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index' }, { :project_id => '1' }]],
+
+ ['/clients',[ { :controller => 'projects', :action => 'index' }]],
+ ['/clients?project_id=1',[ { :controller => 'projects', :action => 'index', :project_id => '1' }]],
+ ['/clients',[ { :controller => 'projects', :action => 'index' }, { :project_id => '1' }]],
+ ['/clients',[ { :action => 'index' }, { :controller => 'projects', :action => 'index', :project_id => '1' }]],
+
+ ['/comment/20',[ { :id => 20 }, { :controller => 'comments', :action => 'show' }]],
+ ['/comment/20',[ { :controller => 'comments', :id => 20, :action => 'show' }]],
+ ['/comments/boo',[ { :controller => 'comments', :action => 'boo' }]],
+
+ ['/ws/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1', :ws => true }]],
+ ['/ws/posts',[ { :controller => 'posts', :action => 'index', :ws => true }]],
+
+ ['/account',[ { :controller => 'account', :action => 'subscription' }]],
+ ['/account/billing',[ { :controller => 'account', :action => 'billing' }]],
+
+ ['/pages/1/notes/show/1',[ { :page_id => '1', :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/pages/1/notes/list',[ { :page_id => '1', :controller => 'notes', :action => 'list' }]],
+ ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes', :action => 'index' }]],
+ ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes' }]],
+ ['/notes',[ { :page_id => nil, :controller => 'notes' }]],
+ ['/notes',[ { :controller => 'notes' }]],
+ ['/notes/print',[ { :controller => 'notes', :action => 'print' }]],
+ ['/notes/print',[ {}, { :controller => 'notes', :action => 'print' }]],
+
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1', :foo => 'bar' }]],
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ { :action => 'index' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ {}, { :controller => 'notes', :id => '1' }]],
+ ['/notes/show/1',[ {}, { :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/notes/index/1',[ { :controller => 'notes', :id => '1' }, { :foo => 'bar' }]],
+ ['/posts',[ { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/notes/list',[ { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' }]],
+
+ ['/posts/ping',[ { :controller => 'posts', :action => 'ping' }]],
+ ['/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1' }]],
+ ['/posts',[ { :controller => 'posts' }]],
+ ['/posts',[ { :controller => 'posts', :action => 'index' }]],
+ ['/posts',[ { :controller => 'posts' }, { :controller => 'posts', :action => 'index' }]],
+ ['/posts/create',[ { :action => 'create' }, { :controller => 'posts' }]],
+ ['/posts?foo=bar',[ { :controller => 'posts', :foo => 'bar' }]],
+ ['/posts?foo%5B%5D=bar&foo%5B%5D=baz', [{ :controller => 'posts', :foo => ['bar', 'baz'] }]],
+ ['/posts?page=2', [{ :controller => 'posts', :page => 2 }]],
+ ['/posts?q%5Bfoo%5D%5Ba%5D=b', [{ :controller => 'posts', :q => { :foo => { :a => 'b'}} }]],
+
+ ['/news.rss', [{ :controller => 'news', :action => 'index', :format => 'rss' }]],
+ ].each_with_index do |(url, params), i|
+ define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do
+ assert_equal url, url_for(@routes, *params), params.inspect
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 1938348375..9685b24c1c 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -851,6 +851,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ # tests the use of dup in url_for
+ def test_url_for_with_no_side_effects
+ # without dup, additional (and possibly unwanted) values will be present in the options (eg. :host)
+ original_options = {:controller => 'projects', :action => 'status'}
+ options = original_options.dup
+
+ url_for options
+
+ # verify that the options passed in have not changed from the original ones
+ assert_equal original_options, options
+ end
+
def test_projects_status
with_test_routes do
assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
index 3f31edd5ce..8be0f452fb 100644
--- a/actionpack/test/template/compiled_templates_test.rb
+++ b/actionpack/test/template/compiled_templates_test.rb
@@ -42,7 +42,7 @@ class CompiledTemplatesTest < Test::Unit::TestCase
def render_without_cache(*args)
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
- view_paths = ActionView::Base.process_view_paths(path)
+ view_paths = ActionView::PathSet.new([path])
ActionView::Base.new(view_paths, {}).render(*args)
end
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index ad31812273..eb569c7308 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -508,25 +508,25 @@ class FormTagHelperTest < ActionView::TestCase
def test_text_area_tag_options_symbolize_keys_side_effects
options = { :option => "random_option" }
- actual = text_area_tag "body", "hello world", options
+ text_area_tag "body", "hello world", options
assert_equal options, { :option => "random_option" }
end
def test_submit_tag_options_symbolize_keys_side_effects
options = { :option => "random_option" }
- actual = submit_tag "submit value", options
+ submit_tag "submit value", options
assert_equal options, { :option => "random_option" }
end
def test_button_tag_options_symbolize_keys_side_effects
options = { :option => "random_option" }
- actual = button_tag "button value", options
+ button_tag "button value", options
assert_equal options, { :option => "random_option" }
end
def test_image_submit_tag_options_symbolize_keys_side_effects
options = { :option => "random_option" }
- actual = image_submit_tag "submit source", options
+ image_submit_tag "submit source", options
assert_equal options, { :option => "random_option" }
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 6f02f8662d..8a582030f6 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -380,7 +380,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
# is not eager loaded
def setup
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
- view_paths = ActionView::Base.process_view_paths(path)
+ view_paths = ActionView::PathSet.new([path])
assert_equal ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH), view_paths.first
setup_view(view_paths)
end
diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb
index f4b5344d63..dfa635335e 100644
--- a/actionpack/test/template/sprockets_helper_test.rb
+++ b/actionpack/test/template/sprockets_helper_test.rb
@@ -205,4 +205,17 @@ class SprocketsHelperTest < ActionView::TestCase
stubs(:asset_environment).returns(assets)
assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", "css")
end
+
+ test "alternate hash based on environment" do
+ assets = Sprockets::Environment.new
+ assets.version = 'development'
+ assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
+ stubs(:asset_environment).returns(assets)
+ dev_path = asset_path("style", "css")
+
+ assets.version = 'production'
+ prod_path = asset_path("style", "css")
+
+ assert_not_equal prod_path, dev_path
+ end
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index d48f2e8a1f..7a910d18e7 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -226,10 +226,10 @@ module ActiveModel
# Send observed_method(object) if the method exists and
# the observer is enabled for the given object's class.
- def update(observed_method, object) #:nodoc:
+ def update(observed_method, object, &block) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
- send(observed_method, object)
+ send(observed_method, object, &block)
end
# Special method sent by the observed class when it is inherited.
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index 99b1f407ae..f6ec24ae57 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -17,6 +17,10 @@ class FooObserver < ActiveModel::Observer
def on_spec(record)
stub.event_with(record) if stub
end
+
+ def around_save(record)
+ yield :in_around_save
+ end
end
class Foo
@@ -133,4 +137,12 @@ class ObserverTest < ActiveModel::TestCase
foo = Foo.new
Foo.send(:notify_observers, :whatever, foo)
end
+
+ test "update passes a block on to the observer" do
+ yielded_value = nil
+ FooObserver.instance.update(:around_save, Foo.new) do |val|
+ yielded_value = val
+ end
+ assert_equal :in_around_save, yielded_value
+ end
end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 8ac4d9a225..2de81c31a3 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -21,6 +21,6 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('arel', '~> 2.1.3')
+ s.add_dependency('arel', '~> 2.2.1')
s.add_dependency('tzinfo', '~> 0.3.29')
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index d63b9e3f24..511d402ee5 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -42,7 +42,7 @@ module ActiveRecord
autoload :ActiveRecordError, 'active_record/errors'
autoload :ConnectionNotEstablished, 'active_record/errors'
autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
-
+
autoload :Aggregations
autoload :Associations
autoload :AttributeMethods
@@ -72,6 +72,7 @@ module ActiveRecord
autoload :Persistence
autoload :QueryCache
autoload :Reflection
+ autoload :Result
autoload :Schema
autoload :SchemaDumper
autoload :Serialization
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index bb519c5703..d1e3ff8e38 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -151,20 +151,20 @@ module ActiveRecord
reset
end
+ def interpolate(sql, record = nil)
+ if sql.respond_to?(:to_proc)
+ owner.send(:instance_exec, record, &sql)
+ else
+ sql
+ end
+ end
+
private
def find_target?
!loaded? && (!owner.new_record? || foreign_key_present?) && klass
end
- def interpolate(sql, record = nil)
- if sql.respond_to?(:to_proc)
- owner.send(:instance_exec, record, &sql)
- else
- sql
- end
- end
-
def creation_attributes
attributes = {}
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 198ad06360..2ee5dbbd70 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -2,6 +2,11 @@ module ActiveRecord
# = Active Record Belongs To Polymorphic Association
module Associations
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
+ def klass
+ type = owner[reflection.foreign_type]
+ type.presence && type.constantize
+ end
+
private
def replace_keys(record)
@@ -17,11 +22,6 @@ module ActiveRecord
reflection.polymorphic_inverse_of(record.class)
end
- def klass
- type = owner[reflection.foreign_type]
- type.presence && type.constantize
- end
-
def raise_on_type_mismatch(record)
# A polymorphic association cannot have a type mismatch, by definition
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index f7ce70db1a..1f917f58f2 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -26,7 +26,7 @@ module ActiveRecord
join_table[reflection.association_foreign_key] => record.id
)
- owner.connection.insert stmt.to_sql
+ owner.connection.insert stmt
end
record
@@ -46,7 +46,7 @@ module ActiveRecord
stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
).compile_delete
- owner.connection.delete stmt.to_sql
+ owner.connection.delete stmt
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 504f25271c..6c878f0f00 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -188,13 +188,12 @@ module ActiveRecord
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
set_target_and_inverse(join_part, association, record)
else
- return if row[join_part.aliased_primary_key].nil?
- association = join_part.instantiate(row)
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
case macro
when :has_many, :has_and_belongs_to_many
other = record.association(join_part.reflection.name)
other.loaded!
- other.target.push(association)
+ other.target.push(association) if association
other.set_inverse_instance(association)
when :belongs_to
set_target_and_inverse(join_part, association, record)
diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
index 24be279449..b77b667219 100644
--- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
@@ -13,7 +13,7 @@ module ActiveRecord
# access the aliased column on the join table
def records_for(ids)
scope = super
- klass.connection.select_all(scope.arel.to_sql, 'SQL', scope.bind_values)
+ klass.connection.select_all(scope.arel, 'SQL', scope.bind_values)
end
def owner_key_name
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 8bd898d126..ed71b5e7d4 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -3,11 +3,10 @@ module ActiveRecord
module PrimaryKey
extend ActiveSupport::Concern
- # Returns this record's primary key value wrapped in an Array or nil if
- # the record is not persisted? or has just been destroyed.
+ # Returns this record's primary key value wrapped in an Array if one is available
def to_key
key = send(self.class.primary_key)
- persisted? && key ? [key] : nil
+ [key] if key
end
module ClassMethods
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index b3a0267276..c76f98d6a0 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -178,7 +178,7 @@ module ActiveRecord #:nodoc:
# <tt>Person.find_all_by_last_name(last_name)</tt>.
#
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
- # <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
+ # <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
# like <tt>Person.find_by_last_name!</tt>.
#
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
@@ -941,17 +941,6 @@ module ActiveRecord #:nodoc:
self.current_scope = nil
end
- # Specifies how the record is loaded by +Marshal+.
- #
- # +_load+ sets an instance variable for each key in the hash it takes as input.
- # Override this method if you require more complex marshalling.
- def _load(data)
- record = allocate
- record.init_with(Marshal.load(data))
- record
- end
-
-
# Finder methods must instantiate through this method to work with the
# single-table inheritance model that makes it possible to create
# objects of different types from the same table.
@@ -1271,27 +1260,43 @@ MSG
self.default_scopes = default_scopes + [scope]
end
- # The @ignore_default_scope flag is used to prevent an infinite recursion situation where
- # a default scope references a scope which has a default scope which references a scope...
def build_default_scope #:nodoc:
- return if defined?(@ignore_default_scope) && @ignore_default_scope
- @ignore_default_scope = true
-
if method(:default_scope).owner != Base.singleton_class
- default_scope
+ evaluate_default_scope { default_scope }
elsif default_scopes.any?
- default_scopes.inject(relation) do |default_scope, scope|
- if scope.is_a?(Hash)
- default_scope.apply_finder_options(scope)
- elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
- default_scope.merge(scope.call)
- else
- default_scope.merge(scope)
+ evaluate_default_scope do
+ default_scopes.inject(relation) do |default_scope, scope|
+ if scope.is_a?(Hash)
+ default_scope.apply_finder_options(scope)
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
+ default_scope.merge(scope.call)
+ else
+ default_scope.merge(scope)
+ end
end
end
end
- ensure
- @ignore_default_scope = false
+ end
+
+ def ignore_default_scope? #:nodoc:
+ Thread.current["#{self}_ignore_default_scope"]
+ end
+
+ def ignore_default_scope=(ignore) #:nodoc:
+ Thread.current["#{self}_ignore_default_scope"] = ignore
+ end
+
+ # The ignore_default_scope flag is used to prevent an infinite recursion situation where
+ # a default scope references a scope which has a default scope which references a scope...
+ def evaluate_default_scope
+ return if ignore_default_scope?
+
+ begin
+ self.ignore_default_scope = true
+ yield
+ ensure
+ self.ignore_default_scope = false
+ end
end
# Returns the class type of the record using the current module as a prefix. So descendants of
@@ -1413,9 +1418,8 @@ MSG
attrs = expand_hash_conditions_for_aggregates(attrs)
table = Arel::Table.new(table_name).alias(default_table_name)
- viz = Arel::Visitors.for(arel_engine)
PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
- viz.accept b
+ connection.visitor.accept b
}.join(' AND ')
end
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -1593,16 +1597,6 @@ MSG
self
end
- # Specifies how the record is dumped by +Marshal+.
- #
- # +_dump+ emits a marshalled hash which has been passed to +encode_with+. Override this
- # method if you require more complex marshalling.
- def _dump(level)
- dump = {}
- encode_with(dump)
- Marshal.dump(dump)
- end
-
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@@ -2169,4 +2163,5 @@ MSG
end
end
+require 'active_record/connection_adapters/abstract/connection_specification'
ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index ddfdb05297..61994d4a47 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -82,10 +82,11 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
- @connections = []
- @checked_out = []
+ @connections = []
+ @checked_out = []
@automatic_reconnect = true
- @tables = {}
+ @tables = {}
+ @visitor = nil
@columns = Hash.new do |h, table_name|
h[table_name] = with_connection do |conn|
@@ -298,8 +299,18 @@ module ActiveRecord
:connected?, :disconnect!, :with => :@connection_mutex
private
+
def new_connection
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
+ connection = ActiveRecord::Base.send(spec.adapter_method, spec.config)
+
+ # TODO: This is a bit icky, and in the long term we may want to change the method
+ # signature for connections. Also, if we switch to have one visitor per
+ # connection (and therefore per thread), we can get rid of the thread-local
+ # variable in Arel::Visitors::ToSql.
+ @visitor ||= connection.class.visitor_for(self)
+ connection.visitor = @visitor
+
+ connection
end
def current_connection_id #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 777ef15dfc..dc4a53034b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -1,30 +1,39 @@
module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
+ # Converts an arel AST to SQL
+ def to_sql(arel)
+ if arel.respond_to?(:ast)
+ visitor.accept(arel.ast)
+ else
+ arel
+ end
+ end
+
# Returns an array of record hashes with the column names as keys and
# column values as values.
- def select_all(sql, name = nil, binds = [])
- select(sql, name, binds)
+ def select_all(arel, name = nil, binds = [])
+ select(to_sql(arel), name, binds)
end
# Returns a record hash with the column names as keys and column values
# as values.
- def select_one(sql, name = nil)
- result = select_all(sql, name)
+ def select_one(arel, name = nil)
+ result = select_all(arel, name)
result.first if result
end
# Returns a single value from a record
- def select_value(sql, name = nil)
- if result = select_one(sql, name)
+ def select_value(arel, name = nil)
+ if result = select_one(arel, name)
result.values.first
end
end
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
- def select_values(sql, name = nil)
- result = select_rows(sql, name)
+ def select_values(arel, name = nil)
+ result = select_rows(to_sql(arel), name)
result.map { |v| v[0] }
end
@@ -74,20 +83,20 @@ module ActiveRecord
#
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds = sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
+ sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
value = exec_insert(sql, name, binds)
id_value || last_inserted_id(value)
end
# Executes the update statement and returns the number of rows affected.
- def update(sql, name = nil, binds = [])
- exec_update(sql, name, binds)
+ def update(arel, name = nil, binds = [])
+ exec_update(to_sql(arel), name, binds)
end
# Executes the delete statement and returns the number of rows affected.
- def delete(sql, name = nil, binds = [])
- exec_delete(sql, name, binds)
+ def delete(arel, name = nil, binds = [])
+ exec_delete(to_sql(arel), name, binds)
end
# Checks whether there is currently no transaction active. This is done
@@ -297,6 +306,16 @@ module ActiveRecord
end
end
+ # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
+ # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
+ # an UPDATE statement, so in the mysql adapters we redefine this to do that.
+ def join_to_update(update, select) #:nodoc:
+ subselect = select.clone
+ subselect.projections = [update.key]
+
+ update.where update.key.in(subselect)
+ end
+
protected
# Returns an array of record hashes with the column names as keys and
# column values as values.
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 093c30aa42..27ff13ad89 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -55,9 +55,10 @@ module ActiveRecord
@query_cache.clear
end
- def select_all(sql, name = nil, binds = [])
+ def select_all(arel, name = nil, binds = [])
if @query_cache_enabled
- cache_sql(sql, binds) { super }
+ sql = to_sql(arel)
+ cache_sql(sql, binds) { super(sql, name, binds) }
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index bde31d1cda..60e2f811a2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -2,21 +2,32 @@ require 'date'
require 'bigdecimal'
require 'bigdecimal/util'
require 'active_support/core_ext/benchmark'
-
-# TODO: Autoload these files
-require 'active_record/connection_adapters/column'
-require 'active_record/connection_adapters/abstract/schema_definitions'
-require 'active_record/connection_adapters/abstract/schema_statements'
-require 'active_record/connection_adapters/abstract/database_statements'
-require 'active_record/connection_adapters/abstract/quoting'
-require 'active_record/connection_adapters/abstract/connection_pool'
-require 'active_record/connection_adapters/abstract/connection_specification'
-require 'active_record/connection_adapters/abstract/query_cache'
-require 'active_record/connection_adapters/abstract/database_limits'
-require 'active_record/result'
+require 'active_support/deprecation'
module ActiveRecord
module ConnectionAdapters # :nodoc:
+ extend ActiveSupport::Autoload
+
+ autoload :Column
+
+ autoload_under 'abstract' do
+ autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
+ autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
+ autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
+
+ autoload :SchemaStatements
+ autoload :DatabaseStatements
+ autoload :DatabaseLimits
+ autoload :Quoting
+
+ autoload :ConnectionPool
+ autoload :ConnectionHandler, 'active_record/connection_adapters/abstract/connection_pool'
+ autoload :ConnectionManagement, 'active_record/connection_adapters/abstract/connection_pool'
+ autoload :ConnectionSpecification
+
+ autoload :QueryCache
+ end
+
# Active Record supports multiple database systems. AbstractAdapter and
# related classes form the abstraction layer which makes this possible.
# An AbstractAdapter represents a connection to a database, and provides an
@@ -38,6 +49,8 @@ module ActiveRecord
define_callbacks :checkout, :checkin
+ attr_accessor :visitor
+
def initialize(connection, logger = nil) #:nodoc:
@active = nil
@connection, @logger = connection, logger
@@ -45,6 +58,25 @@ module ActiveRecord
@query_cache = Hash.new { |h,sql| h[sql] = {} }
@open_transactions = 0
@instrumenter = ActiveSupport::Notifications.instrumenter
+ @visitor = nil
+ end
+
+ # Returns a visitor instance for this adaptor, which conforms to the Arel::ToSql interface
+ def self.visitor_for(pool) # :nodoc:
+ adapter = pool.spec.config[:adapter]
+
+ if Arel::Visitors::VISITORS[adapter]
+ ActiveSupport::Deprecation.warn(
+ "Arel::Visitors::VISITORS is deprecated and will be removed. Database adapters " \
+ "should define a visitor_for method which returns the appropriate visitor for " \
+ "the database. For example, MysqlAdapter.visitor_for(pool) returns " \
+ "Arel::Visitors::MySQL.new(pool)."
+ )
+
+ Arel::Visitors::VISITORS[adapter].new(pool)
+ else
+ Arel::Visitors::ToSql.new(pool)
+ end
end
# Returns the human-readable name of the adapter. Use mixed case - one
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index f9602bbe77..ae5ae16dc7 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -129,6 +129,10 @@ module ActiveRecord
configure_connection
end
+ def self.visitor_for(pool) # :nodoc:
+ Arel::Visitors::MySQL.new(pool)
+ end
+
def adapter_name
ADAPTER_NAME
end
@@ -573,6 +577,27 @@ module ActiveRecord
where_sql
end
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
+ # these, we must use a subquery. However, MySQL is too stupid to create a
+ # temporary table for this automatically, so we have to give it some prompting
+ # in the form of a subsubquery. Ugh!
+ def join_to_update(update, select) #:nodoc:
+ if select.limit || select.offset || select.orders.any?
+ subsubselect = select.clone
+ subsubselect.projections = [update.key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(update.key.name)
+ subselect.from subsubselect.as('__active_record_temp')
+
+ update.where update.key.in(subselect)
+ else
+ update.table select.source
+ update.wheres = select.constraints
+ end
+ end
+
protected
def quoted_columns_for_index(column_names, options = {})
length = options[:length] if options.is_a?(Hash)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 9e6cb13cca..a44ac08ce4 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -192,6 +192,10 @@ module ActiveRecord
connect
end
+ def self.visitor_for(pool) # :nodoc:
+ Arel::Visitors::MySQL.new(pool)
+ end
+
def adapter_name #:nodoc:
ADAPTER_NAME
end
@@ -487,6 +491,27 @@ module ActiveRecord
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
end
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
+ # these, we must use a subquery. However, MySQL is too stupid to create a
+ # temporary table for this automatically, so we have to give it some prompting
+ # in the form of a subsubquery. Ugh!
+ def join_to_update(update, select) #:nodoc:
+ if select.limit || select.offset || select.orders.any?
+ subsubselect = select.clone
+ subsubselect.projections = [update.key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(update.key.name)
+ subselect.from subsubselect.as('__active_record_temp')
+
+ update.where update.key.in(subselect)
+ else
+ update.table select.source
+ update.wheres = select.constraints
+ end
+ end
+
# SCHEMA STATEMENTS ========================================
def structure_dump #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index aefe69f8ed..45c13bdcd6 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -265,6 +265,10 @@ module ActiveRecord
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
end
+ def self.visitor_for(pool) # :nodoc:
+ Arel::Visitors::PostgreSQL.new(pool)
+ end
+
# Clears the prepared statements cache.
def clear_cache!
@statements.each_value do |value|
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index c3a7b039ff..0a0da0b5d3 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,4 +1,6 @@
require 'active_record/connection_adapters/sqlite_adapter'
+
+gem 'sqlite3', '~> 1.3.4'
require 'sqlite3'
module ActiveRecord
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index e2a0f63393..486efc5ba0 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -53,6 +53,10 @@ module ActiveRecord
@config = config
end
+ def self.visitor_for(pool) # :nodoc:
+ Arel::Visitors::SQLite.new(pool)
+ end
+
def adapter_name #:nodoc:
'SQLite'
end
@@ -238,15 +242,15 @@ module ActiveRecord
end
def begin_db_transaction #:nodoc:
- @connection.transaction
+ log('begin transaction',nil) { @connection.transaction }
end
def commit_db_transaction #:nodoc:
- @connection.commit
+ log('commit transaction',nil) { @connection.commit }
end
def rollback_db_transaction #:nodoc:
- @connection.rollback
+ log('rollback transaction',nil) { @connection.rollback }
end
# SCHEMA STATEMENTS ========================================
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 4d387565d9..3c7defedac 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -33,7 +33,7 @@ module ActiveRecord
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
arel_table[counter_name] => object.send(association).count
})
- connection.update stmt.to_sql
+ connection.update stmt
end
return true
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 6cfce6e573..d9ad7e4132 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -70,7 +70,7 @@ module ActiveRecord
# If the locking column has no default value set,
# start the lock version at zero. Note we can't use
- # <tt>locking_enabled?</tt> at this point as
+ # <tt>locking_enabled?</tt> at this point as
# <tt>@attributes</tt> may not have been initialized yet.
if result.key?(self.class.locking_column) && lock_optimistically
@@ -100,7 +100,7 @@ module ActiveRecord
)
).arel.compile_update(arel_attributes_values(false, false, attribute_names))
- affected_rows = connection.update stmt.to_sql
+ affected_rows = connection.update stmt
unless affected_rows == 1
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index fa1b303fc7..7166f1b82a 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -563,7 +563,7 @@ module ActiveRecord
def get_all_versions
table = Arel::Table.new(schema_migrations_table_name)
- Base.connection.select_values(table.project(table['version']).to_sql).map{ |v| v.to_i }.sort
+ Base.connection.select_values(table.project(table['version'])).map{ |v| v.to_i }.sort
end
def current_version
@@ -720,11 +720,11 @@ module ActiveRecord
if down?
@migrated_versions.delete(version)
stmt = table.where(table["version"].eq(version.to_s)).compile_delete
- Base.connection.delete stmt.to_sql
+ Base.connection.delete stmt
else
@migrated_versions.push(version).sort!
stmt = table.compile_insert table["version"] => version.to_s
- Base.connection.insert stmt.to_sql
+ Base.connection.insert stmt
end
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 5a5351b517..fdf17c003c 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -111,7 +111,7 @@ module ActiveRecord
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
unless klass.respond_to?(callback_meth)
klass.send(:define_method, callback_meth) do |&block|
- observer.send(callback, self, &block)
+ observer.update(callback, self, &block)
end
klass.send(callback, callback_meth)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index ebda3875cd..2dac9ea0fb 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -304,7 +304,7 @@ module ActiveRecord
return 0 if attributes_with_values.empty?
klass = self.class
stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
- klass.connection.update stmt.to_sql
+ klass.connection.update stmt
end
# Creates a record with values matching those of the instance attributes
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index fff0ad1b83..15fd1a58c8 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -68,7 +68,7 @@ module ActiveRecord
end
conn.insert(
- im.to_sql,
+ im,
'SQL',
primary_key,
primary_key_value,
@@ -108,10 +108,10 @@ module ActiveRecord
if default_scoped.equal?(self)
@records = if @readonly_value.nil? && !@klass.locking_enabled?
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
+ eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
else
IdentityMap.without do
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
+ eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
end
end
@@ -216,15 +216,21 @@ module ActiveRecord
if conditions || options.present?
where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
else
- stmt = arel.compile_update(Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)))
+ stmt = Arel::UpdateManager.new(arel.engine)
- if limit = arel.limit
- stmt.take limit
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
+ stmt.table(table)
+ stmt.key = table[primary_key]
+
+ if joins_values.any?
+ @klass.connection.join_to_update(stmt, arel)
+ else
+ stmt.take(arel.limit)
+ stmt.order(*arel.orders)
+ stmt.wheres = arel.constraints
end
- stmt.order(*arel.orders)
- stmt.key = table[primary_key]
- @klass.connection.update stmt.to_sql, 'SQL', bind_values
+ @klass.connection.update stmt, 'SQL', bind_values
end
end
@@ -341,8 +347,7 @@ module ActiveRecord
where(conditions).delete_all
else
statement = arel.compile_delete
- affected = @klass.connection.delete(
- statement.to_sql, 'SQL', bind_values)
+ affected = @klass.connection.delete(statement, 'SQL', bind_values)
reset
affected
@@ -388,7 +393,7 @@ module ActiveRecord
end
def to_sql
- @to_sql ||= arel.to_sql
+ @to_sql ||= klass.connection.to_sql(arel)
end
def where_values_hash
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 9a7ff87e88..af86771d2d 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -223,7 +223,7 @@ module ActiveRecord
query_builder = relation.arel
end
- type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
+ type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
@@ -259,7 +259,7 @@ module ActiveRecord
relation = except(:group).group(group.join(','))
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation.to_sql)
+ calculated_data = @klass.connection.select_all(relation)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 8cef4e5554..73368aed18 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -193,8 +193,8 @@ module ActiveRecord
else
relation = relation.where(table[primary_key].eq(id)) if id
end
-
- connection.select_value(relation.to_sql, "#{name} Exists") ? true : false
+
+ connection.select_value(relation, "#{name} Exists") ? true : false
end
protected
@@ -202,7 +202,7 @@ module ActiveRecord
def find_with_associations
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
+ rows = connection.select_all(relation, 'SQL', relation.bind_values)
join_dependency.instantiate(rows)
rescue ThrowResult
[]
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 3942e7bb41..94497e37c7 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -151,7 +151,20 @@ class AdapterTest < ActiveRecord::TestCase
else
@connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
end
+ # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block
+ # and will fail (at least on Oracle)
+ @connection.execute "DELETE FROM fk_test_has_fk"
end
end
end
+
+ def test_deprecated_visitor_for
+ visitor_klass = Class.new(Arel::Visitors::ToSql)
+ Arel::Visitors::VISITORS['fuuu'] = visitor_klass
+ pool = stub(:spec => stub(:config => { :adapter => 'fuuu' }))
+ visitor = assert_deprecated {
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.visitor_for(pool)
+ }
+ assert visitor.is_a?(visitor_klass)
+ end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 24830a661a..490fc5177e 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -39,7 +39,9 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
david = developers(:david)
assert_equal projects(:action_controller), david.projects.find_most_recent
- david = Marshal.load(Marshal.dump(david))
+ marshalled = Marshal.dump(david)
+ david = Marshal.load(marshalled)
+
assert_equal projects(:action_controller), david.projects.find_most_recent
end
@@ -47,7 +49,9 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
david = developers(:david)
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
- david = Marshal.load(Marshal.dump(david))
+ marshalled = Marshal.dump(david)
+ david = Marshal.load(marshalled)
+
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 0b1ba31ac2..5f2328ff95 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -813,4 +813,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert !c.save
end
end
+
+ def test_preloading_empty_through_association_via_joins
+ person = Person.create!(:first_name => "Gaga")
+ person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').includes(:posts).to_a.first
+
+ assert person.posts.loaded?, 'person.posts should be loaded'
+ assert_equal [], person.posts
+ end
end
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index dd450a2a8e..80c6e41169 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -247,7 +247,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins
assert_includes_and_joins_equal(
- Category.where('comments.id' => comments(:more_greetings).id).order('comments.id'),
+ Category.where('comments.id' => comments(:more_greetings).id).order('categories.id'),
[categories(:general), categories(:technology)], :post_comments
)
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 12101c1683..b8ebabfe70 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -144,25 +144,6 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_use_table_engine_for_quoting_where
- relation = Topic.where(Topic.arel_table[:id].eq(1))
- engine = relation.table.engine
-
- fakepool = Class.new(Struct.new(:spec)) {
- def with_connection; yield self; end
- def connection_pool; self; end
- def table_exists?(name); false; end
- def quote_table_name(*args); raise "lol quote_table_name"; end
- }
-
- relation.table.engine = fakepool.new(engine.connection_pool.spec)
-
- error = assert_raises(RuntimeError) { relation.to_a }
- assert_match('lol', error.message)
- ensure
- relation.table.engine = engine
- end
-
def test_preserving_time_objects
assert_kind_of(
Time, Topic.find(1).bonus_time,
@@ -1827,11 +1808,29 @@ class BasicsTest < ActiveRecord::TestCase
def test_marshal_round_trip
expected = posts(:welcome)
- actual = Marshal.load(Marshal.dump(expected))
+ marshalled = Marshal.dump(expected)
+ actual = Marshal.load(marshalled)
assert_equal expected.attributes, actual.attributes
end
+ def test_marshal_new_record_round_trip
+ marshalled = Marshal.dump(Post.new)
+ post = Marshal.load(marshalled)
+
+ assert post.new_record?, "should be a new record"
+ end
+
+ def test_marshalling_with_associations
+ post = Post.new
+ post.comments.build
+
+ marshalled = Marshal.dump(post)
+ post = Marshal.load(marshalled)
+
+ assert_equal 1, post.comments.length
+ end
+
def test_attribute_names
assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
Company.attribute_names
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 42f98b3d42..c38814713a 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -171,7 +171,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_having_condition_from_select
- c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("min_credit_limit > 50").sum(:credit_limit)
+ c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("MIN(credit_limit) > 50").sum(:credit_limit)
assert_nil c[1]
assert_equal 60, c[2]
assert_equal 53, c[9]
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index f92f4e62c5..8a0f453127 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -135,6 +135,10 @@ module ActiveRecord
pool.with_connection
end
end
+
+ def test_pool_sets_connection_visitor
+ assert @pool.connection.visitor.is_a?(Arel::Visitors::ToSql)
+ end
end
end
end
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 643e949087..75e5dfa49b 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -231,6 +231,18 @@ class LifecycleTest < ActiveRecord::TestCase
assert_not_nil observer.topic_ids.last
end
+ test "able to disable observers" do
+ observer = DeveloperObserver.instance # activate
+ observer.calls.clear
+
+ ActiveRecord::Base.observers.disable DeveloperObserver do
+ Developer.create! :name => 'Ancestor', :salary => 100000
+ SpecialDeveloper.create! :name => 'Descendent', :salary => 100000
+ end
+
+ assert_equal [], observer.calls
+ end
+
def test_observer_is_called_once
observer = DeveloperObserver.instance # activate
observer.calls.clear
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index a0cb5dbdc5..0ab4f30363 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -14,7 +14,7 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_set_conditions
Developer.send(:with_scope, :find => { :conditions => 'just a test...' }) do
- assert_match '(just a test...)', Developer.scoped.arel.to_sql
+ assert_match '(just a test...)', Developer.scoped.to_sql
end
end
@@ -274,7 +274,7 @@ class NestedScopingTest < ActiveRecord::TestCase
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
Developer.send(:with_scope, :find => { :limit => 10 }) do
devs = Developer.scoped
- assert_match '(salary = 80000)', devs.arel.to_sql
+ assert_match '(salary = 80000)', devs.to_sql
assert_equal 10, devs.taken
end
end
@@ -308,7 +308,7 @@ class NestedScopingTest < ActiveRecord::TestCase
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
devs = Developer.scoped
- assert_match "(name = 'David') AND (salary = 80000)", devs.arel.to_sql
+ assert_match "(name = 'David') AND (salary = 80000)", devs.to_sql
assert_equal(1, Developer.count)
end
Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
@@ -321,7 +321,7 @@ class NestedScopingTest < ActiveRecord::TestCase
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000', :limit => 10 }) do
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
devs = Developer.scoped
- assert_match "(salary = 80000) AND (name = 'David')", devs.arel.to_sql
+ assert_match "(salary = 80000) AND (name = 'David')", devs.to_sql
assert_equal 10, devs.taken
end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 7e3da145e5..05a41d8a0a 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -26,7 +26,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_to_key_with_primary_key_after_destroy
topic = Topic.find(1)
topic.destroy
- assert_equal nil, topic.to_key
+ assert_equal [1], topic.to_key
end
def test_integer_key
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index f2d177d834..1e2093273e 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -170,7 +170,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
Developer.where('salary = 80000').scoping do
Developer.limit(10).scoping do
devs = Developer.scoped
- assert_match '(salary = 80000)', devs.arel.to_sql
+ assert_match '(salary = 80000)', devs.to_sql
assert_equal 10, devs.taken
end
end
@@ -524,4 +524,22 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count
end
+
+ def test_default_scope_is_threadsafe
+ if in_memory_db?
+ skip "in memory db can't share a db between threads"
+ end
+
+ threads = []
+ assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
+
+ threads << Thread.new do
+ Thread.current[:long_default_scope] = true
+ assert_equal 1, ThreadsafeDeveloper.all.count
+ end
+ threads << Thread.new do
+ assert_equal 1, ThreadsafeDeveloper.all.count
+ end
+ threads.each(&:join)
+ end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 821da91f0a..97abd67385 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -965,4 +965,42 @@ class RelationTest < ActiveRecord::TestCase
def test_ordering_with_extra_spaces
assert_equal authors(:david), Author.order('id DESC , name DESC').last
end
+
+ def test_update_all_with_joins
+ comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id)
+ count = comments.count
+
+ assert_equal count, comments.update_all(:post_id => posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ end
+
+ def test_update_all_with_joins_and_limit
+ comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).limit(1)
+ assert_equal 1, comments.update_all(:post_id => posts(:thinking).id)
+ end
+
+ def test_update_all_with_joins_and_limit_and_order
+ comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('comments.id').limit(1)
+ assert_equal 1, comments.update_all(:post_id => posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:greetings).post
+ assert_equal posts(:welcome), comments(:more_greetings).post
+ end
+
+ def test_update_all_with_joins_and_offset
+ all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id)
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id)
+ end
+
+ def test_update_all_with_joins_and_offset_and_order
+ all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id')
+ count = all_comments.count
+ comments = all_comments.offset(1)
+
+ assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id)
+ assert_equal posts(:thinking), comments(:more_greetings).post
+ assert_equal posts(:welcome), comments(:greetings).post
+ end
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index f182a7fa97..4dc9fff9fd 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -227,3 +227,12 @@ class EagerDeveloperWithCallableDefaultScope < ActiveRecord::Base
default_scope OpenStruct.new(:call => includes(:projects))
end
+
+class ThreadsafeDeveloper < ActiveRecord::Base
+ self.table_name = 'developers'
+
+ def self.default_scope
+ sleep 0.05 if Thread.current[:long_default_scope]
+ limit(1)
+ end
+end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index a25720adbf..dba914da48 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,11 +1,15 @@
*Rails 3.2.0 (unreleased)*
+* Added Array#prepend as an alias for Array#unshift and Array#append as an alias for Array#<< [DHH]
+
+* Removed support for using Module#delegate to delegate to non-public methods [Jon Leighton]
+
* The definition of blank string for Ruby 1.9 has been extended to Unicode whitespace.
Also, in 1.8 the ideographic space U+3000 is considered to be whitespace. [Akira Matsuda, Damien Mathieu]
* The inflector understands acronyms. [dlee]
-* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim]
+* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim]
* Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) [DHH]
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index 4688468a8f..268c9bed4c 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -5,3 +5,4 @@ require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/grouping'
require 'active_support/core_ext/array/random_access'
+require 'active_support/core_ext/array/prepend_and_append'
diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
new file mode 100644
index 0000000000..27718f19d4
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
@@ -0,0 +1,7 @@
+class Array
+ # The human way of thinking about adding stuff to the end of a list is with append
+ alias_method :append, :<<
+
+ # The human way of thinking about adding stuff to the beginning of a list is with prepend
+ alias_method :prepend, :unshift
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/io.rb b/activesupport/lib/active_support/core_ext/io.rb
new file mode 100644
index 0000000000..75f1055191
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/io.rb
@@ -0,0 +1,15 @@
+if RUBY_VERSION < '1.9.2'
+
+# :stopdoc:
+class IO
+ def self.binread(name, length = nil, offset = nil)
+ return File.read name unless length || offset
+ File.open(name, 'rb') { |f|
+ f.seek offset if offset
+ f.read length
+ }
+ end
+end
+# :startdoc:
+
+end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 149b849b12..8350753f78 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,3 +1,6 @@
+require 'active_support/core_ext/object/public_send'
+require 'active_support/core_ext/string/starts_ends_with'
+
class Module
# Provides a delegate class method to easily expose contained objects' methods
# as your own. Pass one or more methods (specified as symbols or strings)
@@ -106,35 +109,49 @@ class Module
unless options.is_a?(Hash) && to = options[:to]
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
end
+ prefix, to, allow_nil = options[:prefix], options[:to], options[:allow_nil]
- if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
+ if prefix == true && to.to_s =~ /^[^a-z_]/
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
end
- prefix = options[:prefix] ? "#{options[:prefix] == true ? to : options[:prefix]}_" : ''
+ method_prefix =
+ if prefix
+ "#{prefix == true ? to : prefix}_"
+ else
+ ''
+ end
file, line = caller.first.split(':', 2)
line = line.to_i
methods.each do |method|
- on_nil =
- if options[:allow_nil]
- 'return'
- else
- %(raise "#{self}##{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- end
+ method = method.to_s
+ call = method.ends_with?('=') ? "public_send(:#{method}, " : "#{method}("
+
+ if allow_nil
+ module_eval(<<-EOS, file, line - 2)
+ def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
+ if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
+ #{to}.#{call}*args, &block) # client.name(*args, &block)
+ end # end
+ end # end
+ EOS
+ else
+ exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- module_eval(<<-EOS, file, line - 5)
- def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
- #{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block)
- rescue NoMethodError # rescue NoMethodError
- if #{to}.nil? # if client.nil?
- #{on_nil} # return # depends on :allow_nil
- else # else
- raise # raise
- end # end
- end # end
- EOS
+ module_eval(<<-EOS, file, line - 1)
+ def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
+ #{to}.#{call}*args, &block) # client.name(*args, &block)
+ rescue NoMethodError # rescue NoMethodError
+ if #{to}.nil? # if client.nil?
+ #{exception} # # add helpful message to the exception
+ else # else
+ raise # raise
+ end # end
+ end # end
+ EOS
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 9ad1e12699..249c2e93c5 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
+require 'active_support/core_ext/object/public_send'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
diff --git a/activesupport/lib/active_support/core_ext/object/public_send.rb b/activesupport/lib/active_support/core_ext/object/public_send.rb
new file mode 100644
index 0000000000..2e77a22c4b
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/public_send.rb
@@ -0,0 +1,25 @@
+require 'active_support/core_ext/kernel/singleton_class'
+
+class Object
+ unless Object.public_method_defined?(:public_send)
+ # Backports Object#public_send from 1.9
+ def public_send(method, *args, &block)
+ # Don't create a singleton class for the object if it doesn't already have one
+ # (This also protects us from classes like Fixnum and Symbol, which cannot have a
+ # singleton class.)
+ klass = singleton_methods.any? ? self.singleton_class : self.class
+
+ if klass.public_method_defined?(method)
+ send(method, *args, &block)
+ else
+ if klass.private_method_defined?(method)
+ raise NoMethodError, "private method `#{method}' called for #{inspect}"
+ elsif klass.protected_method_defined?(method)
+ raise NoMethodError, "protected method `#{method}' called for #{inspect}"
+ else
+ raise NoMethodError, "undefined method `#{method}' for #{inspect}"
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index c5fbbcf890..a15a06d0e4 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,5 +1,6 @@
require 'active_support/duration'
require 'active_support/core_ext/time/zones'
+require 'active_support/core_ext/time/conversions'
class Time
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index 62f9c9aa2e..9651f02c73 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -1,5 +1,6 @@
require 'zlib'
require 'stringio'
+require 'active_support/core_ext/string/encoding'
module ActiveSupport
# A convenient wrapper for the zlib standard library that allows compression/decompression of strings with gzip.
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index 06ceccdb22..daf2a1e1d9 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -54,6 +54,7 @@ module ActiveSupport
inflect.irregular('sex', 'sexes')
inflect.irregular('move', 'moves')
inflect.irregular('cow', 'kine')
+ inflect.irregular('zombie', 'zombies')
inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
end
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 77696eb1db..b5a70d5933 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/delegation'
-
module ActiveSupport
# Notifications provides an instrumentation API for Ruby. To instrument an
# action in Ruby you just need to do:
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 728921a069..4fb487ade1 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -195,12 +195,8 @@ module ActiveSupport
# (GMT). Seconds were chosen as the offset unit because that is the unit that
# Ruby uses to represent time zone offsets (see Time#utc_offset).
def initialize(name, utc_offset = nil, tzinfo = nil)
- begin
- require 'tzinfo'
- rescue LoadError => e
- $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install"
- raise e
- end
+ self.class.send(:require_tzinfo)
+
@name = name
@utc_offset = utc_offset
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
@@ -337,7 +333,12 @@ module ActiveSupport
end
def zones_map
- @zones_map ||= Hash[MAPPING.map { |place, _| [place, create(place)] }]
+ @zones_map ||= begin
+ new_zones_names = MAPPING.keys - lazy_zones_map.keys
+ new_zones = Hash[new_zones_names.map { |place| [place, create(place)] }]
+
+ lazy_zones_map.merge(new_zones)
+ end
end
# Locate a specific time zone object. If the argument is a string, it
@@ -349,7 +350,7 @@ module ActiveSupport
case arg
when String
begin
- zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
+ lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
rescue TZInfo::InvalidTimezoneIdentifier
nil
end
@@ -367,11 +368,28 @@ module ActiveSupport
@us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
end
+ protected
+
+ def require_tzinfo
+ require 'tzinfo'
+ rescue LoadError
+ $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install"
+ raise
+ end
+
private
def lookup(name)
(tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)
end
+
+ def lazy_zones_map
+ require_tzinfo
+
+ @lazy_zones_map ||= Hash.new do |hash, place|
+ hash[place] = create(place) if MAPPING.has_key?(place)
+ end
+ end
end
end
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index e532010b18..f035505a01 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -465,3 +465,13 @@ class ArrayWrapperTests < Test::Unit::TestCase
assert_equal DoubtfulToAry.new.to_ary, Array.wrap(DoubtfulToAry.new)
end
end
+
+class ArrayPrependAppendTest < Test::Unit::TestCase
+ def test_append
+ assert_equal [1, 2], [1].append(2)
+ end
+
+ def test_prepend
+ assert_equal [2, 1], [1].prepend(2)
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/io_test.rb b/activesupport/test/core_ext/io_test.rb
new file mode 100644
index 0000000000..b9abf685da
--- /dev/null
+++ b/activesupport/test/core_ext/io_test.rb
@@ -0,0 +1,23 @@
+require 'abstract_unit'
+
+require 'active_support/core_ext/io'
+
+class IOTest < Test::Unit::TestCase
+ def test_binread_one_arg
+ assert_equal File.read(__FILE__), IO.binread(__FILE__)
+ end
+
+ def test_binread_two_args
+ assert_equal File.read(__FILE__).bytes.first(10).pack('C*'),
+ IO.binread(__FILE__, 10)
+ end
+
+ def test_binread_three_args
+ actual = IO.binread(__FILE__, 5, 10)
+ expected = File.open(__FILE__, 'rb') { |f|
+ f.seek 10
+ f.read 5
+ }
+ assert_equal expected, actual
+ end
+end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index a95cf1591f..d4ce81fdfa 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -26,11 +26,30 @@ module Yz
end
end
-Somewhere = Struct.new(:street, :city)
+Somewhere = Struct.new(:street, :city) do
+ attr_accessor :name
-Someone = Struct.new(:name, :place) do
- delegate :street, :city, :to_f, :to => :place
+ protected
+
+ def protected_method
+ end
+
+ private
+
+ def private_method
+ end
+end
+
+class Someone < Struct.new(:name, :place)
+ delegate :street, :city, :to_f, :protected_method, :private_method, :to => :place
+ delegate :name=, :to => :place, :prefix => true
delegate :upcase, :to => "place.city"
+
+ FAILED_DELEGATE_LINE = __LINE__ + 1
+ delegate :foo, :to => :place
+
+ FAILED_DELEGATE_LINE_2 = __LINE__ + 1
+ delegate :bar, :to => :place, :allow_nil => true
end
Invoice = Struct.new(:client) do
@@ -69,6 +88,19 @@ class ModuleTest < Test::Unit::TestCase
assert_equal "Chicago", @david.city
end
+ def test_delegation_to_assignment_method
+ @david.place_name = "Fred"
+ assert_equal "Fred", @david.place.name
+ end
+
+ def test_delegation_to_protected_method
+ assert_raise(NoMethodError) { @david.protected_method }
+ end
+
+ def test_delegation_to_private_method
+ assert_raise(NoMethodError) { @david.private_method }
+ end
+
def test_delegation_down_hierarchy
assert_equal "CHICAGO", @david.upcase
end
@@ -164,6 +196,24 @@ class ModuleTest < Test::Unit::TestCase
end
end
+ def test_delegation_exception_backtrace
+ someone = Someone.new("foo", "bar")
+ someone.foo
+ rescue NoMethodError => e
+ file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE}"
+ assert e.backtrace.first.include?(file_and_line),
+ "[#{e.backtrace.first}] did not include [#{file_and_line}]"
+ end
+
+ def test_delegation_exception_backtrace_with_allow_nil
+ someone = Someone.new("foo", "bar")
+ someone.bar
+ rescue NoMethodError => e
+ file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE_2}"
+ assert e.backtrace.first.include?(file_and_line),
+ "[#{e.backtrace.first}] did not include [#{file_and_line}]"
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent
diff --git a/activesupport/test/core_ext/object/public_send_test.rb b/activesupport/test/core_ext/object/public_send_test.rb
new file mode 100644
index 0000000000..7dc542e51c
--- /dev/null
+++ b/activesupport/test/core_ext/object/public_send_test.rb
@@ -0,0 +1,117 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object/public_send'
+
+module PublicSendReceiver
+ def receive_public_method(*args)
+ return args + [yield]
+ end
+
+ protected
+
+ def receive_protected_method(*args)
+ return args + [yield]
+ end
+
+ private
+
+ def receive_private_method(*args)
+ return args + [yield]
+ end
+end
+
+# Note, running this on 1.9 will be testing the Ruby core implementation, but it is good to
+# do this to ensure that our backport functions the same as Ruby core in 1.9
+class PublicSendTest < Test::Unit::TestCase
+ def instance
+ @instance ||= begin
+ klass = Class.new do
+ include PublicSendReceiver
+ end
+ klass.new
+ end
+ end
+
+ def singleton_instance
+ @singleton_instance ||= begin
+ object = Object.new
+ object.singleton_class.send(:include, PublicSendReceiver)
+ object
+ end
+ end
+
+ def test_should_receive_public_method
+ assert_equal(
+ [:foo, :bar, :baz],
+ instance.public_send(:receive_public_method, :foo, :bar) { :baz }
+ )
+ end
+
+ def test_should_receive_public_singleton_method
+ assert_equal(
+ [:foo, :bar, :baz],
+ singleton_instance.public_send(:receive_public_method, :foo, :bar) { :baz }
+ )
+ end
+
+ def test_should_raise_on_protected_method
+ assert_raises(NoMethodError) do
+ instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_should_raise_on_protected_singleton_method
+ assert_raises(NoMethodError) do
+ singleton_instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_should_raise_on_private_method
+ assert_raises(NoMethodError) do
+ instance.public_send(:receive_private_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_should_raise_on_singleton_private_method
+ assert_raises(NoMethodError) do
+ singleton_instance.public_send(:receive_private_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_should_raise_on_undefined_method
+ assert_raises(NoMethodError) do
+ instance.public_send(:receive_undefined_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_protected_method_message
+ instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
+ rescue NoMethodError => exception
+ assert_equal(
+ "protected method `receive_protected_method' called for #{instance.inspect}",
+ exception.message
+ )
+ end
+
+ def test_private_method_message
+ instance.public_send(:receive_private_method, :foo, :bar) { :baz }
+ rescue NoMethodError => exception
+ assert_equal(
+ "private method `receive_private_method' called for #{instance.inspect}",
+ exception.message
+ )
+ end
+
+ def test_undefined_method_message
+ instance.public_send(:receive_undefined_method, :foo, :bar) { :baz }
+ rescue NoMethodError => exception
+ assert_equal(
+ "undefined method `receive_undefined_method' for #{instance.inspect}",
+ exception.message
+ )
+ end
+
+ def test_receiver_with_no_singleton
+ assert_equal "5", 5.public_send(:to_s)
+ assert_equal "foo", :foo.public_send(:to_s)
+ end
+end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index cc0dc564f7..884ee61547 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/module/delegation'
module Notifications
class TestCase < ActiveSupport::TestCase
diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb
index d304512ff7..4682ead66e 100644
--- a/railties/guides/rails_guides/generator.rb
+++ b/railties/guides/rails_guides/generator.rb
@@ -199,50 +199,10 @@ module RailsGuides
end
def textile(body, lite_mode=false)
- # If the issue with notextile is fixed just remove the wrapper.
- with_workaround_for_notextile(body) do |body|
- t = RedCloth.new(body)
- t.hard_breaks = false
- t.lite_mode = lite_mode
- t.to_html(:notestuff, :plusplus, :code)
- end
- end
-
- # For some reason the notextile tag does not always turn off textile. See
- # LH ticket of the security guide (#7). As a temporary workaround we deal
- # with code blocks by hand.
- def with_workaround_for_notextile(body)
- code_blocks = []
-
- body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m|
- brush = case $1
- when 'ruby', 'sql', 'plain'
- $1
- when 'erb'
- 'ruby; html-script: true'
- when 'html'
- 'xml' # html is understood, but there are .xml rules in the CSS
- else
- 'plain'
- end
-
- code_blocks.push(<<HTML)
-<notextile>
-<div class="code_container">
-<pre class="brush: #{brush}; gutter: false; toolbar: false">
-#{ERB::Util.h($2).strip}
-</pre>
-</div>
-</notextile>
-HTML
- "\ndirty_workaround_for_notextile_#{code_blocks.size - 1}\n"
- end
-
- body = yield body
-
- body.gsub(%r{<p>dirty_workaround_for_notextile_(\d+)</p>}) do |_|
- code_blocks[$1.to_i]
- end
+ t = RedCloth.new(body)
+ t.hard_breaks = false
+ t.lite_mode = lite_mode
+ t.to_html(:notestuff, :plusplus, :code)
end
def warn_about_broken_links(html)
diff --git a/railties/guides/rails_guides/textile_extensions.rb b/railties/guides/rails_guides/textile_extensions.rb
index b3e0e32357..4677fae504 100644
--- a/railties/guides/rails_guides/textile_extensions.rb
+++ b/railties/guides/rails_guides/textile_extensions.rb
@@ -33,11 +33,30 @@ module RailsGuides
body.gsub!('<plus>', '+')
end
+ def brush_for(code_type)
+ case code_type
+ when 'ruby', 'sql', 'plain'
+ code_type
+ when 'erb'
+ 'ruby; html-script: true'
+ when 'html'
+ 'xml' # html is understood, but there are .xml rules in the CSS
+ else
+ 'plain'
+ end
+ end
+
def code(body)
body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m|
- es = ERB::Util.h($2)
- css_class = $1.in?(['erb', 'shell']) ? 'html' : $1
- %{<notextile><div class="code_container"><code class="#{css_class}">#{es}</code></div></notextile>}
+ <<HTML
+<notextile>
+<div class="code_container">
+<pre class="brush: #{brush_for($1)}; gutter: false; toolbar: false">
+#{ERB::Util.h($2).strip}
+</pre>
+</div>
+</notextile>
+HTML
end
end
end
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index e1fa374aca..df863935cf 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -452,6 +452,30 @@ Examples of +in?+:
NOTE: Defined in +active_support/core_ext/object/inclusion.rb+.
+h4. +public_send+
+
+This method is available by default in Ruby 1.9, and is backported to Ruby 1.8 by Active Support. Like the regular +send+ method, +public_send+ allows you to call a method when the name is not known until runtime. However, if the method is not public then a +NoMethodError+ exception will be raised.
+
+<ruby>
+class Greeter
+ def hello(who)
+ "Hello " + who
+ end
+
+ private
+
+ def secret
+ "sauce"
+ end
+end
+
+greeter = Greeter.new
+greeter.public_send(:hello, 'Jim') # => "Hello Jim"
+greeter.public_send(:secret) # => NoMethodError
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/object/public_send.rb+.
+
h3. Extensions to +Module+
h4. +alias_method_chain+
@@ -864,7 +888,9 @@ end
It is shorter, and the intention more obvious.
-The macro accepts several methods:
+The method must be public in the target.
+
+The +delegate+ macro accepts several methods:
<ruby>
delegate :name, :age, :address, :twitter, :to => :profile
diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile
index 0c8e4e974d..5a6343472c 100644
--- a/railties/guides/source/i18n.textile
+++ b/railties/guides/source/i18n.textile
@@ -1,4 +1,4 @@
-lh2. Rails Internationalization (I18n) API
+h2. Rails Internationalization (I18n) API
The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for *translating your application to a single custom language* other than English or for *providing multi-language support* in your application.
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 975e159999..cd850b6a75 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -37,6 +37,7 @@ module Rails
@assets.paths = []
@assets.precompile = [ /\w+\.(?!js|css).+/, /application.(css|js)$/ ]
@assets.prefix = "/assets"
+ @assets.version = ''
@assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/" ]
@assets.js_compressor = nil
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 06ed890e05..de56d47688 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -15,8 +15,8 @@
config.assets.compress = true
# Specifies the header that your server uses for sending files
- # (comment out if your front-end server doesn't support this)
- config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index fa1548db8b..b7b344a62d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -3,10 +3,12 @@
# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.
-# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
-ActionController::Base.wrap_parameters <%= key_value :format, "[:json]" %>
+<%= app_const %>.configure do
+ # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ config.action_controller.wrap_parameters = { <%= key_value :format, "[:json]" %> }
-# Disable root element in JSON by default.
-if defined?(ActiveRecord)
- ActiveRecord::Base.include_root_in_json = false
+ <%- unless options.skip_active_record? -%>
+ # Disable root element in JSON by default.
+ config.active_record.include_root_in_json = false
+ <%- end -%>
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index c46422437d..4baa2110e7 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -11,8 +11,6 @@ module Rails
def app
if mountable?
directory "app"
- template "app/views/layouts/application.html.erb.tt",
- "app/views/layouts/#{name}/application.html.erb"
empty_directory_with_gitkeep "app/assets/images/#{name}"
elsif full?
empty_directory_with_gitkeep "app/models"
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt
index 01550dec2f..01550dec2f 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 8c88b25617..e8fb1f3d98 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -2,6 +2,7 @@ require 'rails/initializable'
require 'rails/configuration'
require 'active_support/inflector'
require 'active_support/core_ext/module/introspection'
+require 'active_support/core_ext/module/delegation'
module Rails
# Railtie is the core of the Rails framework and provides several hooks to extend
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index 5d613e0698..ca8875ad9b 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -8,6 +8,8 @@ end
# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
class RDocTaskWithoutDescriptions < RDoc::Task
+ include ::Rake::DSL
+
def define
task rdoc_task_name
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 802b737483..a8d1382e94 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -46,10 +46,11 @@ module ApplicationTests
assert defined?(Uglifier)
end
- test "precompile creates the file and gives it the original asset's content" do
+ test "precompile creates the file, gives it the original asset's content and run in production as default" do
app_file "app/assets/javascripts/application.js", "alert();"
app_file "app/assets/javascripts/foo/application.js", "alert();"
+ ENV["RAILS_ENV"] = nil
capture(:stdout) do
Dir.chdir(app_path){ `bundle exec rake assets:precompile` }
end
@@ -57,15 +58,26 @@ module ApplicationTests
files << Dir["#{app_path}/public/assets/foo/application-*.js"].first
files.each do |file|
assert_not_nil file, "Expected application.js asset to be generated, but none found"
- assert_equal "alert();\n", File.read(file)
+ assert_equal "alert()", File.read(file)
end
end
- test "precompile appends the md5 hash to files referenced with asset_path" do
+ test "precompile appends the md5 hash to files referenced with asset_path and run in the provided RAILS_ENV" do
app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
+ # capture(:stdout) do
+ Dir.chdir(app_path){ `bundle exec rake assets:precompile RAILS_ENV=test` }
+ # end
+ file = Dir["#{app_path}/public/assets/application-*.css"].first
+ assert_match /\/assets\/rails-([0-z]+)\.png/, File.read(file)
+ end
+
+ test "precompile appends the md5 hash to files referenced with asset_path and run in production as default even using RAILS_GROUPS=assets" do
+ app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
+
+ ENV["RAILS_ENV"] = nil
capture(:stdout) do
- Dir.chdir(app_path){ `bundle exec rake assets:precompile` }
+ Dir.chdir(app_path){ `bundle exec rake assets:precompile RAILS_GROUPS=assets` }
end
file = Dir["#{app_path}/public/assets/application-*.css"].first
assert_match /\/assets\/rails-([0-z]+)\.png/, File.read(file)
@@ -80,7 +92,7 @@ module ApplicationTests
Dir.chdir(app_path){ `bundle exec rake assets:clean` }
end
- files = Dir["#{app_path}/public/assets/**/*"]
+ files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/*"]
assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}"
end
diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb
index c7a1c573f9..d2ad2668bb 100644
--- a/railties/test/application/middleware/sendfile_test.rb
+++ b/railties/test/application/middleware/sendfile_test.rb
@@ -27,11 +27,12 @@ module ApplicationTests
end
# x_sendfile_header middleware
- test "config.action_dispatch.x_sendfile_header defaults to ''" do
+ test "config.action_dispatch.x_sendfile_header defaults to nil" do
make_basic_app
simple_controller
get "/"
+ assert !last_response.headers["X-Sendfile"]
assert_equal File.read(__FILE__), last_response.body
end
diff --git a/tasks/release.rb b/tasks/release.rb
index 01950b227d..2422efa786 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -4,11 +4,11 @@ root = File.expand_path('../../', __FILE__)
version = File.read("#{root}/RAILS_VERSION").strip
tag = "v#{version}"
-directory "dist"
+directory "pkg"
(FRAMEWORKS + ['rails']).each do |framework|
namespace framework do
- gem = "dist/#{framework}-#{version}.gem"
+ gem = "pkg/#{framework}-#{version}.gem"
gemspec = "#{framework}.gemspec"
task :clean do
@@ -41,10 +41,10 @@ directory "dist"
File.open(file, 'w') { |f| f.write ruby }
end
- task gem => %w(update_version_rb dist) do
+ task gem => %w(update_version_rb pkg) do
cmd = ""
cmd << "cd #{framework} && " unless framework == "rails"
- cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/dist/"
+ cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/"
sh cmd
end
@@ -104,14 +104,14 @@ namespace :all do
end
task :commit do
- File.open('dist/commit_message.txt', 'w') do |f|
+ File.open('pkg/commit_message.txt', 'w') do |f|
f.puts "# Preparing for #{version} release\n"
f.puts
f.puts "# UNCOMMENT THE LINE ABOVE TO APPROVE THIS COMMIT"
end
- sh "git add . && git commit --verbose --template=dist/commit_message.txt"
- rm_f "dist/commit_message.txt"
+ sh "git add . && git commit --verbose --template=pkg/commit_message.txt"
+ rm_f "pkg/commit_message.txt"
end
task :tag do