aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGonçalo Silva <goncalossilva@gmail.com>2010-07-10 17:36:10 +0100
committerGonçalo Silva <goncalossilva@gmail.com>2010-07-10 17:36:10 +0100
commitcd2bbed9846d84a1230a1b9e52843eedca17b28d (patch)
tree5214b7855f3d102e4c22239b9d62bc5717cb3547
parentd2c633ba0bfb7baacdee89a46d7d036d24c68817 (diff)
parent80e47d7b88dcc732ebeb5290faab6e529829dac6 (diff)
downloadrails-cd2bbed9846d84a1230a1b9e52843eedca17b28d.tar.gz
rails-cd2bbed9846d84a1230a1b9e52843eedca17b28d.tar.bz2
rails-cd2bbed9846d84a1230a1b9e52843eedca17b28d.zip
Merge branch 'master' of http://github.com/rails/rails
-rw-r--r--.gitignore1
-rw-r--r--actionmailer/lib/action_mailer/base.rb4
-rw-r--r--actionmailer/test/base_test.rb1
-rw-r--r--actionmailer/test/old_base/url_test.rb12
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb4
-rw-r--r--actionpack/lib/action_controller/base.rb6
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb5
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb10
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing.rb12
-rw-r--r--actionpack/lib/action_dispatch/routing/deprecated_mapper.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb168
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb76
-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_helper.rb12
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb10
-rw-r--r--actionpack/test/abstract/helper_test.rb2
-rw-r--r--actionpack/test/abstract_unit.rb25
-rw-r--r--actionpack/test/controller/helper_test.rb6
-rw-r--r--actionpack/test/controller/mime_responds_test.rb20
-rw-r--r--actionpack/test/controller/url_for_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb277
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb22
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb16
-rw-r--r--actionpack/test/template/url_helper_test.rb29
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/errors.rb1
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb160
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb39
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/sanitizer.rb23
-rw-r--r--activemodel/lib/active_model/observing.rb2
-rw-r--r--activemodel/test/cases/mass_assignment_security/black_list_test.rb28
-rw-r--r--activemodel/test/cases/mass_assignment_security/permission_set_test.rb30
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb37
-rw-r--r--activemodel/test/cases/mass_assignment_security/white_list_test.rb28
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb52
-rw-r--r--activemodel/test/models/mass_assignment_specific.rb57
-rw-r--r--activerecord/lib/active_record/association_preload.rb7
-rw-r--r--activerecord/lib/active_record/associations.rb19
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb14
-rw-r--r--activerecord/lib/active_record/base.rb179
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb19
-rw-r--r--activerecord/lib/active_record/counter_cache.rb17
-rw-r--r--activerecord/lib/active_record/named_scope.rb2
-rw-r--r--activerecord/lib/active_record/observer.rb6
-rw-r--r--activerecord/lib/active_record/persistence.rb19
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation.rb3
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/timestamp.rb29
-rw-r--r--activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb120
-rw-r--r--activerecord/test/cases/associations/eager_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations_test.rb10
-rw-r--r--activerecord/test/cases/base_test.rb193
-rw-r--r--activerecord/test/cases/counter_cache_test.rb19
-rw-r--r--activerecord/test/cases/dirty_test.rb7
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb43
-rw-r--r--activerecord/test/cases/migration_test.rb14
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb24
-rw-r--r--activerecord/test/fixtures/cars.yml4
-rw-r--r--activerecord/test/models/car.rb4
-rw-r--r--activerecord/test/models/category.rb2
-rw-r--r--activerecord/test/models/engine.rb3
-rw-r--r--activerecord/test/models/loose_person.rb24
-rw-r--r--activerecord/test/models/post.rb1
-rw-r--r--activerecord/test/models/wheel.rb3
-rw-r--r--activerecord/test/schema/schema.rb13
-rw-r--r--activesupport/lib/active_support/all.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb57
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/date/zones.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/logger.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/extending.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb5
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb5
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb5
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb24
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb11
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb1
-rw-r--r--activesupport/lib/active_support/json/encoding.rb42
-rw-r--r--activesupport/lib/active_support/json/variable.rb6
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb26
-rw-r--r--activesupport/lib/active_support/time.rb1
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb12
-rw-r--r--activesupport/test/core_ext/class_test.rb40
-rw-r--r--activesupport/test/core_ext/object_and_class_ext_test.rb49
-rw-r--r--activesupport/test/deprecation_test.rb20
-rw-r--r--activesupport/test/descendants_tracker_test.rb8
-rw-r--r--activesupport/test/json/encoding_test.rb6
-rw-r--r--activesupport/test/multibyte_chars_test.rb41
-rw-r--r--activesupport/test/ordered_hash_test.rb1
-rw-r--r--rails.gemspec2
-rw-r--r--railties/README4
-rw-r--r--railties/guides/source/3_0_release_notes.textile4
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile4
-rw-r--r--railties/guides/source/active_support_core_extensions.textile81
-rw-r--r--railties/guides/source/association_basics.textile10
-rw-r--r--railties/guides/source/getting_started.textile2
-rw-r--r--railties/guides/source/index.html.erb2
-rw-r--r--railties/guides/source/initialization.textile215
-rw-r--r--railties/guides/source/layouts_and_rendering.textile2
-rw-r--r--railties/guides/source/plugins.textile2
-rw-r--r--railties/guides/source/routing.textile32
-rw-r--r--railties/guides/source/security.textile2
-rw-r--r--railties/lib/rails/application.rb23
-rw-r--r--railties/lib/rails/commands.rb10
-rw-r--r--railties/lib/rails/commands/application.rb2
-rw-r--r--railties/lib/rails/commands/plugin.rb109
-rw-r--r--railties/lib/rails/commands/runner.rb2
-rw-r--r--railties/lib/rails/commands/server.rb3
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt4
-rw-r--r--railties/lib/rails/info_routes.rb2
-rw-r--r--railties/lib/rails/tasks/middleware.rake2
-rw-r--r--railties/lib/rails/tasks/routes.rake2
-rw-r--r--railties/railties.gemspec2
-rw-r--r--railties/test/application/initializers/frameworks_test.rb21
-rw-r--r--railties/test/application/initializers/i18n_test.rb2
-rw-r--r--railties/test/application/rake_test.rb4
-rw-r--r--railties/test/application/runner_test.rb2
-rw-r--r--railties/test/rails_info_controller_test.rb2
143 files changed, 1978 insertions, 1080 deletions
diff --git a/.gitignore b/.gitignore
index 16bffc157b..2dfdf96e7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.gem
pkg
.bundle
+Gemfile.lock
debug.log
doc/rdoc
activemodel/doc
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 11fa978b9c..ed4bea0c77 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -737,13 +737,13 @@ module ActionMailer #:nodoc:
raise "You can no longer call ActionMailer::Base.default_url_options " \
"directly. You need to set config.action_mailer.default_url_options. " \
"If you are using ActionMailer standalone, you need to include the " \
- "url_helpers of a router directly."
+ "routing url_helpers directly."
end
end
# This module will complain if the user tries to set default_url_options
# directly instead of through the config object. In Action Mailer's Railtie,
- # we include the url_helpers of the router, which will override this module
+ # we include the router's url_helpers, which will override this module.
extend DeprecatedUrlOptions
ActiveSupport.run_load_hooks(:action_mailer, self)
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 88e38a583b..5bc0491cff 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require 'abstract_unit'
+require 'active_support/time'
class BaseTest < ActiveSupport::TestCase
# TODO Add some tests for implicity layout render and url helpers
diff --git a/actionmailer/test/old_base/url_test.rb b/actionmailer/test/old_base/url_test.rb
index 6895fb230e..b6496bfe1b 100644
--- a/actionmailer/test/old_base/url_test.rb
+++ b/actionmailer/test/old_base/url_test.rb
@@ -28,7 +28,7 @@ class UrlTestMailer < ActionMailer::Base
end
end
-class ActionMailerUrlTest < Test::Unit::TestCase
+class ActionMailerUrlTest < ActionMailer::TestCase
def encode( text, charset="UTF-8" )
quoted_printable( text, charset )
@@ -57,10 +57,12 @@ class ActionMailerUrlTest < Test::Unit::TestCase
def test_signed_up_with_url
UrlTestMailer.delivery_method = :test
-
- AppRoutes.draw do |map|
- map.connect ':controller/:action/:id'
- map.welcome 'welcome', :controller=>"foo", :action=>"bar"
+
+ assert_deprecated do
+ AppRoutes.draw do |map|
+ map.connect ':controller/:action/:id'
+ map.welcome 'welcome', :controller=>"foo", :action=>"bar"
+ end
end
expected = new_mail
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index b4311599a9..df0b5ac9a2 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -27,5 +27,5 @@ Gem::Specification.new do |s|
s.add_dependency('rack-test', '~> 0.5.4')
#s.add_dependency('rack-mount', '~> 0.6.6')
s.add_dependency('tzinfo', '~> 0.3.16')
- s.add_dependency('erubis', '~> 2.6.5')
+ s.add_dependency('erubis', '~> 2.6.6')
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 6e3998aa21..b81d5954eb 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -50,8 +50,8 @@ module AbstractController
if controller.respond_to?(:_helpers)
include controller._helpers
- if controller.respond_to?(:_router)
- include controller._router.url_helpers
+ if controller.respond_to?(:_routes)
+ include controller._routes.url_helpers
end
# TODO: Fix RJS to not require this
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index a70ba0d2e3..1a2cbaab65 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -60,17 +60,11 @@ module ActionController
include ActionController::Compatibility
def self.inherited(klass)
- ::ActionController::Base.subclasses << klass.to_s
super
klass.helper :all
end
- def self.subclasses
- @subclasses ||= []
- end
-
config_accessor :asset_host, :asset_path
-
ActiveSupport.run_load_hooks(:action_controller, self)
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 89201fb5ee..e0bc47318a 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
module ActionController
@@ -64,7 +65,7 @@ module ActionController
def helpers_dir=(value)
ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead", caller
- self.helpers_path = Array(value)
+ self.helpers_path = Array.wrap(value)
end
# Declares helper accessors for controller attributes. For example, the
@@ -103,7 +104,7 @@ module ActionController
# Extract helper names from files in app/helpers/**/*_helper.rb
def all_application_helpers
helpers = []
- helpers_path.each do |path|
+ Array.wrap(helpers_path).each do |path|
extract = /^#{Regexp.quote(path.to_s)}\/?(.*)_helper.rb$/
helpers += Dir["#{path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index c465035ca1..a51fc5b8e4 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -12,17 +12,17 @@ module ActionController
).merge(:script_name => request.script_name)
end
- def _router
- raise "In order to use #url_for, you must include the helpers of a particular " \
- "router. For instance, `include Rails.application.routes.url_helpers"
+ def _routes
+ raise "In order to use #url_for, you must include routing helpers explicitly. " \
+ "For instance, `include Rails.application.routes.url_helpers"
end
module ClassMethods
def action_methods
@action_methods ||= begin
- super - _router.named_routes.helper_names
+ super - _routes.named_routes.helper_names
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 86395c4d93..9261422f0b 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -2,7 +2,6 @@ require "rails"
require "action_controller"
require "action_dispatch/railtie"
require "action_view/railtie"
-require "active_support/core_ext/class/subclasses"
require "active_support/deprecation/proxy_wrappers"
require "active_support/deprecation"
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 08bc80dbc2..64f4d1d532 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -64,6 +64,11 @@ module ActionDispatch
super(key.to_s, value)
end
+ def clear
+ load_for_write!
+ super
+ end
+
def to_hash
load_for_read!
h = {}.replace(self)
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 401d98b663..c664fb0bc2 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -31,7 +31,7 @@ module ActionDispatch
# Think of creating routes as drawing a map for your requests. The map tells
# them where to go based on some predefined pattern:
#
- # AppName::Applications.routes.draw do |map|
+ # AppName::Application.routes.draw do |map|
# Pattern 1 tells some request to go to one place
# Pattern 2 tell them to go to another
# ...
@@ -62,7 +62,7 @@ module ActionDispatch
#
# redirect_to show_item_path(:id => 25)
#
- # Use <tt>root</tt> as a shorthand to name a route for the root path "".
+ # Use <tt>root</tt> as a shorthand to name a route for the root path "/".
#
# # In routes.rb
# root :to => 'blogs#index'
@@ -72,7 +72,7 @@ module ActionDispatch
#
# # and provide these named routes
# root_url # => 'http://www.example.com/'
- # root_path # => ''
+ # root_path # => '/'
#
# Note: when using +controller+, the route is simply named after the
# method you call on the block parameter rather than map.
@@ -91,9 +91,7 @@ module ActionDispatch
#
# Routes can generate pretty URLs. For example:
#
- # match '/articles/:year/:month/:day', :constraints => {
- # :controller => 'articles',
- # :action => 'find_by_date',
+ # match '/articles/:year/:month/:day' => 'articles#find_by_id', :constraints => {
# :year => /\d{4}/,
# :month => /\d{1,2}/,
# :day => /\d{1,2}/
@@ -167,7 +165,7 @@ module ActionDispatch
#
# You can reload routes if you feel you must:
#
- # Rails::Application.reload_routes!
+ # Rails.application.reload_routes!
#
# This will clear all named routes and reload routes.rb if the file has been modified from
# last load. To absolutely force reloading, use <tt>reload!</tt>.
diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
index bc3f62fb48..b3146a1c60 100644
--- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
@@ -19,9 +19,9 @@ module ActionDispatch
def in_memory_controller_namespaces
namespaces = Set.new
- ActionController::Base.subclasses.each do |klass|
- controller_name = klass.underscore
- namespaces << controller_name.split('/')[0...-1].join('/')
+ ActionController::Base.descendants.each do |klass|
+ next if klass.anonymous?
+ namespaces << klass.name.underscore.split('/')[0...-1].join('/')
end
namespaces.delete('')
namespaces
@@ -31,7 +31,7 @@ module ActionDispatch
class DeprecatedMapper #:nodoc:
def initialize(set) #:nodoc:
ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " <<
- "Please check how to update your router file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
+ "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
@set = set
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 0b4ba8c9f5..430f6fc5a3 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -13,6 +13,8 @@ module ActionDispatch
end
end
+ attr_reader :app
+
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
end
@@ -63,6 +65,16 @@ module ActionDispatch
end
end
+ if path.match(':controller')
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
+
+ # Add a default constraint for :controller path segments that matches namespaced
+ # controllers with default routes like :controller/:action/:id(.:format), e.g:
+ # GET /admin/products/show/1
+ # => { :controller => 'admin/products', :action => 'show', :id => '1' }
+ options.reverse_merge!(:controller => /.+?/)
+ end
+
path = normalize_path(path)
path_without_format = path.sub(/\(\.:format\)$/, '')
@@ -133,8 +145,11 @@ module ActionDispatch
defaults[:controller] ||= default_controller
defaults[:action] ||= default_action
- defaults.delete(:controller) if defaults[:controller].blank?
- defaults.delete(:action) if defaults[:action].blank?
+ defaults.delete(:controller) if defaults[:controller].blank? || defaults[:controller].is_a?(Regexp)
+ defaults.delete(:action) if defaults[:action].blank? || defaults[:action].is_a?(Regexp)
+
+ defaults[:controller] = defaults[:controller].to_s if defaults.key?(:controller)
+ defaults[:action] = defaults[:action].to_s if defaults.key?(:action)
if defaults[:controller].blank? && segment_keys.exclude?("controller")
raise ArgumentError, "missing :controller"
@@ -183,15 +198,15 @@ module ActionDispatch
def default_controller
if @options[:controller]
- @options[:controller].to_s
+ @options[:controller]
elsif @scope[:controller]
- @scope[:controller].to_s
+ @scope[:controller]
end
end
def default_action
if @options[:action]
- @options[:action].to_s
+ @options[:action]
end
end
end
@@ -431,12 +446,16 @@ module ActionDispatch
end
def merge_options_scope(parent, child)
- (parent || {}).merge(child)
+ (parent || {}).except(*override_keys(child)).merge(child)
end
def merge_shallow_scope(parent, child)
child ? true : false
end
+
+ def override_keys(child)
+ child.key?(:only) || child.key?(:except) ? [:only, :except] : []
+ end
end
module Resources
@@ -444,7 +463,7 @@ module ActionDispatch
# a path appended since they fit properly in their scope level.
VALID_ON_OPTIONS = [:new, :collection, :member]
CANONICAL_ACTIONS = [:index, :create, :new, :show, :update, :destroy]
- MERGE_FROM_SCOPE_OPTIONS = [:shallow, :constraints]
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
class Resource #:nodoc:
DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
@@ -464,9 +483,9 @@ module ActionDispatch
end
def actions
- if only = options[:only]
+ if only = @options[:only]
Array(only).map(&:to_sym)
- elsif except = options[:except]
+ elsif except = @options[:except]
default_actions - Array(except).map(&:to_sym)
else
default_actions
@@ -489,68 +508,31 @@ module ActionDispatch
singular
end
- alias_method :nested_name, :member_name
-
# Checks for uncountable plurals, and appends "_index" if they're.
def collection_name
singular == plural ? "#{plural}_index" : plural
end
- def shallow?
- options[:shallow] ? true : false
- end
-
- def constraints
- options[:constraints] || {}
- end
-
- def id_constraint?
- options[:id] && options[:id].is_a?(Regexp) || constraints[:id] && constraints[:id].is_a?(Regexp)
- end
-
- def id_constraint
- options[:id] || constraints[:id]
- end
-
- def collection_options
- (options || {}).dup.tap do |opts|
- opts.delete(:id)
- opts[:constraints] = options[:constraints].dup if options[:constraints]
- opts[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
- end
- end
-
- def nested_path
- "#{path}/:#{singular}_id"
- end
-
- def nested_options
- {}.tap do |opts|
- opts[:as] = member_name
- opts["#{singular}_id".to_sym] = id_constraint if id_constraint?
- opts[:options] = { :shallow => shallow? } unless options[:shallow].nil?
- end
- end
-
def resource_scope
- [{ :controller => controller }]
+ { :controller => controller }
end
def collection_scope
- [path, collection_options]
+ path
end
def member_scope
- ["#{path}/:id", options]
+ "#{path}/:id"
end
def new_scope(new_path)
- ["#{path}/#{new_path}"]
+ "#{path}/#{new_path}"
end
def nested_scope
- [nested_path, nested_options]
+ "#{path}/:#{singular}_id"
end
+
end
class SingletonResource < Resource #:nodoc:
@@ -559,7 +541,7 @@ module ActionDispatch
def initialize(entities, options)
@name = entities.to_s
@path = options.delete(:path) || @name
- @controller = (options.delete(:controller) || @name.to_s.pluralize).to_s
+ @controller = (options.delete(:controller) || plural).to_s
@as = options.delete(:as)
@options = options
end
@@ -569,24 +551,10 @@ module ActionDispatch
end
alias :collection_name :member_name
- def nested_path
- path
- end
-
- def nested_options
- {}.tap do |opts|
- opts[:as] = member_name
- opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil?
- end
- end
-
- def shallow?
- false
- end
-
def member_scope
- [path, options]
+ path
end
+ alias :nested_scope :member_scope
end
def initialize(*args) #:nodoc:
@@ -693,18 +661,18 @@ module ActionDispatch
end
with_scope_level(:nested) do
- if parent_resource.shallow?
+ if shallow?
with_exclusive_scope do
if @scope[:shallow_path].blank?
- scope(*parent_resource.nested_scope) { yield }
+ scope(parent_resource.nested_scope, nested_options) { yield }
else
scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
- scope(*parent_resource.nested_scope) { yield }
+ scope(parent_resource.nested_scope, nested_options) { yield }
end
end
end
else
- scope(*parent_resource.nested_scope) { yield }
+ scope(parent_resource.nested_scope, nested_options) { yield }
end
end
end
@@ -723,6 +691,10 @@ module ActionDispatch
end
end
+ def shallow?
+ parent_resource.instance_of?(Resource) && @scope[:shallow]
+ end
+
def match(*args)
options = args.extract_options!.dup
options[:anchor] = true unless options.key?(:anchor)
@@ -800,16 +772,17 @@ module ActionDispatch
return true
end
- if path_names = options.delete(:path_names)
- scope(:path_names => path_names) do
+ scope_options = options.slice!(*RESOURCE_OPTIONS)
+ unless scope_options.empty?
+ scope(scope_options) do
send(method, resources.pop, options, &block)
end
return true
end
- scope_options = @scope.slice(*MERGE_FROM_SCOPE_OPTIONS).delete_if{ |k,v| v.blank? }
- options.reverse_merge!(scope_options) unless scope_options.empty?
- options.reverse_merge!(@scope[:options]) unless @scope[:options].blank?
+ unless action_options?(options)
+ options.merge!(scope_action_options) if scope_action_options?
+ end
if resource_scope?
nested do
@@ -821,6 +794,18 @@ module ActionDispatch
false
end
+ def action_options?(options)
+ options[:only] || options[:except]
+ end
+
+ def scope_action_options?
+ @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
+ end
+
+ def scope_action_options
+ @scope[:options].slice(:only, :except)
+ end
+
def resource_scope?
[:resource, :resources].include?(@scope[:scope_level])
end
@@ -853,7 +838,7 @@ module ActionDispatch
def resource_scope(resource)
with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
- scope(*parent_resource.resource_scope) do
+ scope(parent_resource.resource_scope) do
yield
end
end
@@ -861,7 +846,7 @@ module ActionDispatch
def new_scope
with_scope_level(:new) do
- scope(*parent_resource.new_scope(action_path(:new))) do
+ scope(parent_resource.new_scope(action_path(:new))) do
yield
end
end
@@ -869,7 +854,7 @@ module ActionDispatch
def collection_scope
with_scope_level(:collection) do
- scope(*parent_resource.collection_scope) do
+ scope(parent_resource.collection_scope) do
yield
end
end
@@ -877,18 +862,33 @@ module ActionDispatch
def member_scope
with_scope_level(:member) do
- scope(*parent_resource.member_scope) do
+ scope(parent_resource.member_scope) do
yield
end
end
end
+ def nested_options
+ {}.tap do |options|
+ options[:as] = parent_resource.member_name
+ options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
+ end
+ end
+
+ def id_constraint?
+ @scope[:id].is_a?(Regexp) || (@scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp))
+ end
+
+ def id_constraint
+ @scope[:id] || @scope[:constraints][:id]
+ end
+
def canonical_action?(action, flag)
flag && CANONICAL_ACTIONS.include?(action)
end
def shallow_scoping?
- parent_resource && parent_resource.shallow? && @scope[:scope_level] == :member
+ shallow? && @scope[:scope_level] == :member
end
def path_for_action(action, path)
@@ -932,7 +932,7 @@ module ActionDispatch
collection_name = parent_resource.collection_name
member_name = parent_resource.member_name
name_prefix = "#{name_prefix}_" if name_prefix.present?
- end
+ end
case @scope[:scope_level]
when :collection
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 5ecad6bc04..1b1a221c60 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -26,7 +26,7 @@ module ActionDispatch
return [404, {'X-Cascade' => 'pass'}, []]
end
- controller.action(params[:action]).call(env)
+ dispatch(controller, params[:action], env)
end
def prepare_params!(params)
@@ -34,29 +34,43 @@ module ActionDispatch
split_glob_param!(params) if @glob_param
end
- def controller(params, raise_error=true)
+ # If this is a default_controller (i.e. a controller specified by the user)
+ # we should raise an error in case it's not found, because it usually means
+ # an user error. However, if the controller was retrieved through a dynamic
+ # segment, as in :controller(/:action), we should simply return nil and
+ # delegate the control back to Rack cascade. Besides, if this is not a default
+ # controller, it means we should respect the @scope[:module] parameter.
+ def controller(params, default_controller=true)
if params && params.key?(:controller)
controller_param = params[:controller]
- unless controller = @controllers[controller_param]
- controller_name = "#{controller_param.camelize}Controller"
- controller = @controllers[controller_param] =
- ActiveSupport::Dependencies.ref(controller_name)
- end
-
- controller.get
+ controller_reference(controller_param)
end
rescue NameError => e
- raise ActionController::RoutingError, e.message, e.backtrace if raise_error
+ raise ActionController::RoutingError, e.message, e.backtrace if default_controller
end
- private
- def merge_default_action!(params)
- params[:action] ||= 'index'
- end
+ private
- def split_glob_param!(params)
- params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
+ def controller_reference(controller_param)
+ unless controller = @controllers[controller_param]
+ controller_name = "#{controller_param.camelize}Controller"
+ controller = @controllers[controller_param] =
+ ActiveSupport::Dependencies.ref(controller_name)
end
+ controller.get
+ end
+
+ def dispatch(controller, action, env)
+ controller.action(action).call(env)
+ end
+
+ def merge_default_action!(params)
+ params[:action] ||= 'index'
+ end
+
+ def split_glob_param!(params)
+ params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
+ end
end
# A NamedRouteCollection instance is a collection of named routes, and also
@@ -268,10 +282,10 @@ module ActionDispatch
# Yes plz - JP
included do
routes.install_helpers(self)
- singleton_class.send(:define_method, :_router) { routes }
+ singleton_class.send(:define_method, :_routes) { routes }
end
- define_method(:_router) { routes }
+ define_method(:_routes) { routes }
end
helpers
@@ -302,7 +316,6 @@ module ActionDispatch
@extras = extras
normalize_options!
- normalize_recall!
normalize_controller_action_id!
use_relative_controller!
controller.sub!(%r{^/}, '') if controller
@@ -319,7 +332,11 @@ module ActionDispatch
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
- @options[key] = @recall.delete(key)
+ if named_route_exists?
+ @options[key] = @recall.delete(key) if segment_keys.include?(key)
+ else
+ @options[key] = @recall.delete(key)
+ end
end
end
@@ -343,15 +360,6 @@ module ActionDispatch
end
end
- def normalize_recall!
- # If the target route is not a standard route then remove controller and action
- # from the options otherwise they will appear in the url parameters
- if block_or_proc_route_target?
- recall.delete(:controller) unless segment_keys.include?(:controller)
- recall.delete(:action) unless segment_keys.include?(:action)
- end
- end
-
# This pulls :controller, :action, and :id out of the recall.
# The recall key is only used if there is no key in the options
# or if the key in the options is identical. If any of
@@ -424,12 +432,8 @@ module ActionDispatch
named_route && set.named_routes[named_route]
end
- def block_or_proc_route_target?
- named_route_exists? && !set.named_routes[named_route].app.is_a?(Dispatcher)
- end
-
def segment_keys
- named_route_exists? ? set.named_routes[named_route].segment_keys : []
+ set.named_routes[named_route].segment_keys
end
end
@@ -474,7 +478,7 @@ module ActionDispatch
path_options = yield(path_options) if block_given?
path = generate(path_options, path_segments || {})
- # ROUTES TODO: This can be called directly, so script_name should probably be set in the router
+ # ROUTES TODO: This can be called directly, so script_name should probably be set in the routes
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
@@ -506,6 +510,8 @@ module ActionDispatch
end
dispatcher = route.app
+ dispatcher = dispatcher.app while dispatcher.is_a?(Mapper::Constraints)
+
if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false)
dispatcher.prepare_params!(params)
return params
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index db17615d92..980abd44df 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -128,7 +128,7 @@ module ActionDispatch
when String
options
when nil, Hash
- _router.url_for(url_options.merge((options || {}).symbolize_keys))
+ _routes.url_for(url_options.merge((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 956c88a553..20a2e7c1f0 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -173,7 +173,7 @@ module ActionView #:nodoc:
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
class_attribute :helpers
- class_attribute :_router
+ class_attribute :_routes
class << self
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index d1b10a9281..6302491c2a 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -213,12 +213,22 @@ module ActionView
# ...
# <% end %>
#
- # And for namespaced routes, like +admin_post_url+:
+ # For namespaced routes, like +admin_post_url+:
#
# <%= form_for([:admin, @post]) do |f| %>
# ...
# <% end %>
#
+ # If your resource has associations defined, for example, you want to add comments
+ # to the post given that the routes are set correctly:
+ #
+ # <%= form_for([@document, @comment]) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # Where +@document = Document.find(params[:id])+ and
+ # +@comment = Comment.new+.
+ #
# === Unobtrusive JavaScript
#
# Specifying:
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index b8d6dc22f2..0e3cc54fd1 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -12,7 +12,7 @@ module ActionView
# and controllers.
module UrlHelper
# This helper may be included in any class that includes the
- # URL helpers of a router (router.url_helpers). Some methods
+ # URL helpers of a routes (routes.url_helpers). Some methods
# provided here will only work in the4 context of a request
# (link_to_unless_current, for instance), which must be provided
# as a method called #request on the context.
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index e5614c9e3b..56aebca957 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -151,7 +151,7 @@ module ActionView
@view ||= begin
view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
view.singleton_class.send :include, _helpers
- view.singleton_class.send :include, @controller._router.url_helpers
+ view.singleton_class.send :include, @controller._routes.url_helpers
view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"
view.extend(Locals)
view.locals = self.locals
@@ -192,13 +192,13 @@ module ActionView
end
end
- def _router
- @controller._router if @controller.respond_to?(:_router)
+ def _routes
+ @controller._routes if @controller.respond_to?(:_routes)
end
def method_missing(selector, *args)
- if @controller.respond_to?(:_router) &&
- @controller._router.named_routes.helpers.include?(selector)
+ if @controller.respond_to?(:_routes) &&
+ @controller._routes.named_routes.helpers.include?(selector)
@controller.__send__(selector, *args)
else
super
diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb
index 0cdf5c2298..15c27501f2 100644
--- a/actionpack/test/abstract/helper_test.rb
+++ b/actionpack/test/abstract/helper_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-ActionController::Base.helpers_path = [File.dirname(__FILE__) + '/../fixtures/helpers']
+ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__)
module AbstractController
module Testing
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index c8477fb83f..5be47f7c96 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -41,7 +41,7 @@ require 'pp' # require 'pp' early to prevent hidden_methods from not picking up
module Rails
end
-# Monkey patch the old router initialization to be silenced.
+# Monkey patch the old routes initialization to be silenced.
class ActionDispatch::Routing::DeprecatedMapper
def initialize_with_silencer(*args)
ActiveSupport::Deprecation.silence { initialize_without_silencer(*args) }
@@ -182,13 +182,16 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase
self.app = build_app
- class StubDispatcher
- def self.new(*args)
- lambda { |env|
- params = env['action_dispatch.request.path_parameters']
- controller, action = params[:controller], params[:action]
- [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
- }
+ # Stub Rails dispatcher so it does not get controller references and
+ # simply return the controller#action as Rack::Body.
+ class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
+ protected
+ def controller_reference(controller_param)
+ controller_param
+ end
+
+ def dispatch(controller, action, env)
+ [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
end
end
@@ -275,9 +278,9 @@ end
class ActionController::Base
def self.test_routes(&block)
- router = ActionDispatch::Routing::RouteSet.new
- router.draw(&block)
- include router.url_helpers
+ routes = ActionDispatch::Routing::RouteSet.new
+ routes.draw(&block)
+ include routes.url_helpers
end
end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index c9a6e5dd45..9b9657929f 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/kernel/reporting'
-ActionController::Base.helpers_path = [File.dirname(__FILE__) + '/../fixtures/helpers']
+ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__)
module Fun
class GamesController < ActionController::Base
@@ -106,7 +106,7 @@ class HelperTest < ActiveSupport::TestCase
end
def test_all_helpers_with_alternate_helper_dir
- @controller_class.helpers_path = [File.dirname(__FILE__) + '/../fixtures/alternate_helpers']
+ @controller_class.helpers_path = File.expand_path('../../fixtures/alternate_helpers', __FILE__)
# Reload helpers
@controller_class._helpers = Module.new
@@ -143,7 +143,7 @@ class HelperTest < ActiveSupport::TestCase
assert_equal ["some/foo/bar"], ActionController::Base.helpers_dir
end
ensure
- ActionController::Base.helpers_path = [File.dirname(__FILE__) + '/../fixtures/helpers']
+ ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__)
end
private
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index d654338dba..b5ce391b61 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -36,6 +36,15 @@ class RespondToController < ActionController::Base
type.all { render :text => "Nothing" }
end
end
+
+ def json_xml_or_html
+ respond_to do |type|
+ type.json { render :text => 'JSON' }
+ type.xml { render :xml => 'XML' }
+ type.html { render :text => 'HTML' }
+ end
+ end
+
def forced_xml
request.format = :xml
@@ -364,6 +373,17 @@ class RespondToControllerTest < ActionController::TestCase
get :handle_any_any
assert_equal 'Whatever you ask for, I got it', @response.body
end
+
+ def test_browser_check_with_any_any
+ @request.accept = "application/json, application/xml"
+ get :json_xml_or_html
+ assert_equal 'JSON', @response.body
+
+ @request.accept = "application/json, application/xml, */*"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+ end
+
def test_rjs_type_skips_layout
@request.accept = "text/javascript"
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 4b30b0af36..71a4a43477 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -120,7 +120,7 @@ module AbstractController
def test_relative_url_root_is_respected
# ROUTES TODO: Tests should not have to pass :relative_url_root directly. This
- # should probably come from the router.
+ # should probably come from routes.
add_host!
assert_equal('https://www.basecamphq.com/subdir/c/a/i',
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 26bd641cd6..2a014bf976 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -45,7 +45,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
match 'account/logout' => redirect("/logout"), :as => :logout_redirect
match 'account/login', :to => redirect("/login")
- match 'account/overview'
+ constraints(lambda { |req| true }) do
+ match 'account/overview'
+ end
+
match '/account/nested/overview'
match 'sign_in' => "sessions#new"
@@ -298,8 +301,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
+ resource :token, :module => :api
scope :module => :api do
- resource :token
resources :errors, :shallow => true do
resources :notices
end
@@ -310,11 +313,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
match '/' => 'mes#index'
end
- namespace :private do
- root :to => redirect('/private/index')
- match "index", :to => 'private#index'
- end
-
get "(/:username)/followers" => "followers#index"
get "/groups(/user/:username)" => "groups#index"
get "(/user/:username)/photos" => "photos#index"
@@ -325,7 +323,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- match "whatever/:controller(/:action(/:id))"
+ match "whatever/:controller(/:action(/:id))", :id => /\d+/
resource :profile do
get :settings
@@ -348,6 +346,63 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ namespace :private do
+ root :to => redirect('/private/index')
+ match "index", :to => 'private#index'
+ end
+
+ scope :only => [:index, :show] do
+ namespace :only do
+ resources :clubs do
+ resources :players
+ resource :chairman
+ end
+ end
+ end
+
+ scope :except => [:new, :create, :edit, :update, :destroy] do
+ namespace :except do
+ resources :clubs do
+ resources :players
+ resource :chairman
+ end
+ end
+ end
+
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :companies do
+ scope :only => :index do
+ resources :divisions
+ end
+ scope :except => [:show, :update, :destroy] do
+ resources :departments
+ end
+ end
+ resource :leader
+ resources :managers, :except => [:show, :update, :destroy]
+ end
+ end
+ end
+
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :companies do
+ scope :except => [:show, :update, :destroy] do
+ resources :divisions
+ end
+ scope :only => :index do
+ resources :departments
+ end
+ end
+ resource :leader
+ resources :managers, :only => :index
+ end
+ end
+ end
+
match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
end
end
@@ -470,6 +525,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_namespace_with_controller_segment
+ assert_raise(ArgumentError) do
+ self.class.stub_controllers do |routes|
+ routes.draw do
+ namespace :admin do
+ match '/:controller(/:action(/:id(.:format)))'
+ end
+ end
+ end
+ end
+ end
+
def test_session_singleton_resource
with_test_routes do
get '/session'
@@ -1240,7 +1307,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'pass', @response.headers['X-Cascade']
get '/products/0001/images'
assert_equal 'images#index', @response.body
- get '/products/0001/images/1'
+ get '/products/0001/images/0001'
assert_equal 'images#show', @response.body
get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
@@ -1285,6 +1352,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_url_generator_for_namespaced_generic_route
+ with_test_routes do
+ get 'whatever/foo/bar/show'
+ assert_equal 'foo/bar#show', @response.body
+
+ get 'whatever/foo/bar/show/1'
+ assert_equal 'foo/bar#show', @response.body
+
+ assert_equal 'http://www.example.com/whatever/foo/bar/show',
+ url_for(:controller => "foo/bar", :action => "show")
+
+ assert_equal 'http://www.example.com/whatever/foo/bar/show/1',
+ url_for(:controller => "foo/bar", :action => "show", :id => '1')
+ end
+ end
+
def test_assert_recognizes_account_overview
with_test_routes do
assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview")
@@ -1628,6 +1711,182 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_only_should_be_read_from_scope
+ with_test_routes do
+ get '/only/clubs'
+ assert_equal 'only/clubs#index', @response.body
+ assert_equal '/only/clubs', only_clubs_path
+
+ get '/only/clubs/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_path(:id => '1') }
+
+ get '/only/clubs/1/players'
+ assert_equal 'only/players#index', @response.body
+ assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1')
+
+ get '/only/clubs/1/players/2/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') }
+
+ get '/only/clubs/1/chairman'
+ assert_equal 'only/chairmen#show', @response.body
+ assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1')
+
+ get '/only/clubs/1/chairman/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') }
+ end
+ end
+
+ def test_except_should_be_read_from_scope
+ with_test_routes do
+ get '/except/clubs'
+ assert_equal 'except/clubs#index', @response.body
+ assert_equal '/except/clubs', except_clubs_path
+
+ get '/except/clubs/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_path(:id => '1') }
+
+ get '/except/clubs/1/players'
+ assert_equal 'except/players#index', @response.body
+ assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1')
+
+ get '/except/clubs/1/players/2/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') }
+
+ get '/except/clubs/1/chairman'
+ assert_equal 'except/chairmen#show', @response.body
+ assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1')
+
+ get '/except/clubs/1/chairman/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') }
+ end
+ end
+
+ def test_only_option_should_override_scope
+ with_test_routes do
+ get '/only/sectors'
+ assert_equal 'only/sectors#index', @response.body
+ assert_equal '/only/sectors', only_sectors_path
+
+ get '/only/sectors/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_path(:id => '1') }
+ end
+ end
+
+ def test_only_option_should_not_inherit
+ with_test_routes do
+ get '/only/sectors/1/companies/2'
+ assert_equal 'only/companies#show', @response.body
+ assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2')
+
+ get '/only/sectors/1/leader'
+ assert_equal 'only/leaders#show', @response.body
+ assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1')
+ end
+ end
+
+ def test_except_option_should_override_scope
+ with_test_routes do
+ get '/except/sectors'
+ assert_equal 'except/sectors#index', @response.body
+ assert_equal '/except/sectors', except_sectors_path
+
+ get '/except/sectors/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_path(:id => '1') }
+ end
+ end
+
+ def test_except_option_should_not_inherit
+ with_test_routes do
+ get '/except/sectors/1/companies/2'
+ assert_equal 'except/companies#show', @response.body
+ assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2')
+
+ get '/except/sectors/1/leader'
+ assert_equal 'except/leaders#show', @response.body
+ assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1')
+ end
+ end
+
+ def test_except_option_should_override_scoped_only
+ with_test_routes do
+ get '/only/sectors/1/managers'
+ assert_equal 'only/managers#index', @response.body
+ assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1')
+
+ get '/only/sectors/1/managers/2'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') }
+ end
+ end
+
+ def test_only_option_should_override_scoped_except
+ with_test_routes do
+ get '/except/sectors/1/managers'
+ assert_equal 'except/managers#index', @response.body
+ assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1')
+
+ get '/except/sectors/1/managers/2'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') }
+ end
+ end
+
+ def test_only_scope_should_override_parent_scope
+ with_test_routes do
+ get '/only/sectors/1/companies/2/divisions'
+ assert_equal 'only/divisions#index', @response.body
+ assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/only/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+ end
+
+ def test_except_scope_should_override_parent_scope
+ with_test_routes do
+ get '/except/sectors/1/companies/2/divisions'
+ assert_equal 'except/divisions#index', @response.body
+ assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/except/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+ end
+
+ def test_except_scope_should_override_parent_only_scope
+ with_test_routes do
+ get '/only/sectors/1/companies/2/departments'
+ assert_equal 'only/departments#index', @response.body
+ assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2')
+
+ get '/only/sectors/1/companies/2/departments/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+ end
+
+ def test_only_scope_should_override_parent_except_scope
+ with_test_routes do
+ get '/except/sectors/1/companies/2/departments'
+ assert_equal 'except/departments#index', @response.body
+ assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2')
+
+ get '/except/sectors/1/companies/2/departments/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+ end
+
private
def with_test_routes
yield
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index fd63f5ad5e..f0e01bfff0 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -30,6 +30,11 @@ class CookieStoreTest < ActionController::IntegrationTest
render :text => "id: #{request.session_options[:id]}"
end
+ def call_session_clear
+ session.clear
+ head :ok
+ end
+
def call_reset_session
reset_session
head :ok
@@ -175,6 +180,23 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
+ def test_setting_session_value_after_session_clear
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ session_payload = response.body
+ assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
+ headers['Set-Cookie']
+
+ get '/call_session_clear'
+ assert_response :success
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
def test_persistent_session_id
with_test_route_set do
cookies[SessionKey] = SignedBar
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index 326b336a1a..f83651d583 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -2,24 +2,24 @@ require 'abstract_unit'
module TestUrlGeneration
class WithMountPoint < ActionDispatch::IntegrationTest
- Router = ActionDispatch::Routing::RouteSet.new
- Router.draw { match "/foo", :to => "my_route_generating#index", :as => :foo }
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw { match "/foo", :to => "my_route_generating#index", :as => :foo }
class ::MyRouteGeneratingController < ActionController::Base
- include Router.url_helpers
+ include Routes.url_helpers
def index
render :text => foo_path
end
end
- include Router.url_helpers
+ include Routes.url_helpers
- def _router
- Router
+ def _routes
+ Routes
end
def app
- Router
+ Routes
end
test "generating URLS normally" do
@@ -30,7 +30,7 @@ module TestUrlGeneration
assert_equal "/bar/foo", foo_path(:script_name => "/bar")
end
- test "the request's SCRIPT_NAME takes precedence over the router's" do
+ test "the request's SCRIPT_NAME takes precedence over the routes'" do
get "/foo", {}, 'SCRIPT_NAME' => "/new"
assert_equal "/new/foo", response.body
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 765beebfa3..048f96c9a9 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -406,6 +406,14 @@ end
class UrlHelperControllerTest < ActionController::TestCase
class UrlHelperController < ActionController::Base
test_routes do |map|
+ match 'url_helper_controller_test/url_helper/show/:id',
+ :to => 'url_helper_controller_test/url_helper#show',
+ :as => :show
+
+ match 'url_helper_controller_test/url_helper/profile/:name',
+ :to => 'url_helper_controller_test/url_helper#show',
+ :as => :profile
+
match 'url_helper_controller_test/url_helper/show_named_route',
:to => 'url_helper_controller_test/url_helper#show_named_route',
:as => :show_named_route
@@ -418,6 +426,14 @@ class UrlHelperControllerTest < ActionController::TestCase
:as => :normalize_recall_params
end
+ def show
+ if params[:name]
+ render :inline => 'ok'
+ else
+ redirect_to profile_path(params[:id])
+ end
+ end
+
def show_url_for
render :inline => "<%= url_for :controller => 'url_helper_controller_test/url_helper', :action => 'show_url_for' %>"
end
@@ -484,15 +500,24 @@ class UrlHelperControllerTest < ActionController::TestCase
assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body
end
- def test_recall_params_should_be_normalized_when_using_block_route
+ def test_recall_params_should_be_normalized
get :normalize_recall_params
assert_equal '/url_helper_controller_test/url_helper/normalize_recall_params', @response.body
end
- def test_recall_params_should_not_be_changed_when_using_normal_route
+ def test_recall_params_should_not_be_changed
get :recall_params_not_changed
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
+
+ def test_recall_params_should_normalize_id
+ get :show, :id => '123'
+ assert_equal 302, @response.status
+ assert_equal 'http://test.host/url_helper_controller_test/url_helper/profile/123', @response.location
+
+ get :show, :name => '123'
+ assert_equal 'ok', @response.body
+ end
end
class TasksController < ActionController::Base
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 026430fee3..5ed21a39c2 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -38,6 +38,7 @@ module ActiveModel
autoload :EachValidator, 'active_model/validator'
autoload :Errors
autoload :Lint
+ autoload :MassAssignmentSecurity
autoload :Name, 'active_model/naming'
autoload :Naming
autoload :Observer, 'active_model/observing'
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index d42fc5291d..482b3dac47 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/reverse_merge'
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
new file mode 100644
index 0000000000..66cd9fdde6
--- /dev/null
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -0,0 +1,160 @@
+require 'active_support/core_ext/class/attribute.rb'
+require 'active_model/mass_assignment_security/permission_set'
+
+module ActiveModel
+ # = Active Model Mass-Assignment Security
+ module MassAssignmentSecurity
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :_accessible_attributes
+ class_attribute :_protected_attributes
+ class_attribute :_active_authorizer
+ end
+
+ # Mass assignment security provides an interface for protecting attributes
+ # from end-user assignment. For more complex permissions, mass assignment security
+ # may be handled outside the model by extending a non-ActiveRecord class,
+ # such as a controller, with this behavior.
+ #
+ # For example, a logged in user may need to assign additional attributes depending
+ # on their role:
+ #
+ # class AccountsController < ApplicationController
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessible :first_name, :last_name
+ #
+ # def self.admin_accessible_attributes
+ # accessible_attributes + [ :plan_id ]
+ # end
+ #
+ # def update
+ # ...
+ # @account.update_attributes(account_params)
+ # ...
+ # end
+ #
+ # protected
+ #
+ # def account_params
+ # sanitize_for_mass_assignment(params[:account])
+ # end
+ #
+ # def mass_assignment_authorizer
+ # admin ? admin_accessible_attributes : super
+ # end
+ #
+ # end
+ #
+ module ClassMethods
+ # Attributes named in this macro are protected from mass-assignment
+ # whenever attributes are sanitized before assignment.
+ #
+ # Mass-assignment to these attributes will simply be ignored, to assign
+ # to them you can use direct writer methods. This is meant to protect
+ # sensitive attributes from being overwritten by malicious users
+ # tampering with URLs or forms.
+ #
+ # == Example
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name, :credit_rating
+ # attr_protected :credit_rating
+ #
+ # def attributes=(values)
+ # sanitize_for_mass_assignment(values).each do |k, v|
+ # send("#{k}=", v)
+ # end
+ # end
+ # end
+ #
+ # customer = Customer.new
+ # customer.attributes = { "name" => "David", "credit_rating" => "Excellent" }
+ # customer.name # => "David"
+ # customer.credit_rating # => nil
+ #
+ # customer.credit_rating = "Average"
+ # customer.credit_rating # => "Average"
+ #
+ # To start from an all-closed default and enable attributes as needed,
+ # have a look at +attr_accessible+.
+ #
+ # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_protected+
+ # to sanitize attributes won't provide sufficient protection.
+ def attr_protected(*names)
+ self._protected_attributes = self.protected_attributes + names
+ self._active_authorizer = self._protected_attributes
+ end
+
+ # Specifies a white list of model attributes that can be set via
+ # mass-assignment.
+ #
+ # This is the opposite of the +attr_protected+ macro: Mass-assignment
+ # will only set attributes in this list, to assign to the rest of
+ # attributes you can use direct writer methods. This is meant to protect
+ # sensitive attributes from being overwritten by malicious users
+ # tampering with URLs or forms. If you'd rather start from an all-open
+ # default and restrict attributes as needed, have a look at
+ # +attr_protected+.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name, :credit_rating
+ # attr_accessible :name
+ #
+ # def attributes=(values)
+ # sanitize_for_mass_assignment(values).each do |k, v|
+ # send("#{k}=", v)
+ # end
+ # end
+ # end
+ #
+ # customer = Customer.new
+ # customer.attributes = { :name => "David", :credit_rating => "Excellent" }
+ # customer.name # => "David"
+ # customer.credit_rating # => nil
+ #
+ # customer.credit_rating = "Average"
+ # customer.credit_rating # => "Average"
+ #
+ # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_accessible+
+ # to sanitize attributes won't provide sufficient protection.
+ def attr_accessible(*names)
+ self._accessible_attributes = self.accessible_attributes + names
+ self._active_authorizer = self._accessible_attributes
+ end
+
+ def protected_attributes
+ self._protected_attributes ||= BlackList.new(attributes_protected_by_default).tap do |w|
+ w.logger = self.logger if self.respond_to?(:logger)
+ end
+ end
+
+ def accessible_attributes
+ self._accessible_attributes ||= WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) }
+ end
+
+ def active_authorizer
+ self._active_authorizer ||= protected_attributes
+ end
+
+ def attributes_protected_by_default
+ []
+ end
+ end
+
+ protected
+
+ def sanitize_for_mass_assignment(attributes)
+ mass_assignment_authorizer.sanitize(attributes)
+ end
+
+ def mass_assignment_authorizer
+ self.class.active_authorizer
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
new file mode 100644
index 0000000000..7c48472799
--- /dev/null
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -0,0 +1,39 @@
+require 'active_model/mass_assignment_security/sanitizer'
+
+module ActiveModel
+ module MassAssignmentSecurity
+ class PermissionSet < Set
+ attr_accessor :logger
+
+ def +(values)
+ super(values.map(&:to_s))
+ end
+
+ def include?(key)
+ super(remove_multiparameter_id(key))
+ end
+
+ protected
+
+ def remove_multiparameter_id(key)
+ key.to_s.gsub(/\(.+/, '')
+ end
+ end
+
+ class WhiteList < PermissionSet
+ include Sanitizer
+
+ def deny?(key)
+ !include?(key)
+ end
+ end
+
+ class BlackList < PermissionSet
+ include Sanitizer
+
+ def deny?(key)
+ include?(key)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
new file mode 100644
index 0000000000..150beb1ff2
--- /dev/null
+++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
@@ -0,0 +1,23 @@
+module ActiveModel
+ module MassAssignmentSecurity
+ module Sanitizer
+ # Returns all attributes not denied by the authorizer.
+ def sanitize(attributes)
+ sanitized_attributes = attributes.reject { |key, value| deny?(key) }
+ debug_protected_attribute_removal(attributes, sanitized_attributes)
+ sanitized_attributes
+ end
+
+ protected
+
+ def debug_protected_attribute_removal(attributes, sanitized_attributes)
+ removed_keys = attributes.keys - sanitized_attributes.keys
+ warn!(removed_keys) if removed_keys.any?
+ end
+
+ def warn!(attrs)
+ self.logger.debug "WARNING: Can't mass-assign protected attributes: #{attrs.join(', ')}" if self.logger
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index d0f36ce3b1..c6a79acf81 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -1,6 +1,7 @@
require 'singleton'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/aliasing'
+require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/string/inflections'
module ActiveModel
@@ -157,6 +158,7 @@ module ActiveModel
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
+ remove_possible_method(:observed_classes)
define_method(:observed_classes) { models }
end
diff --git a/activemodel/test/cases/mass_assignment_security/black_list_test.rb b/activemodel/test/cases/mass_assignment_security/black_list_test.rb
new file mode 100644
index 0000000000..ed168bc016
--- /dev/null
+++ b/activemodel/test/cases/mass_assignment_security/black_list_test.rb
@@ -0,0 +1,28 @@
+require "cases/helper"
+
+class BlackListTest < ActiveModel::TestCase
+
+ def setup
+ @black_list = ActiveModel::MassAssignmentSecurity::BlackList.new
+ @included_key = 'admin'
+ @black_list += [ @included_key ]
+ end
+
+ test "deny? is true for included items" do
+ assert_equal true, @black_list.deny?(@included_key)
+ end
+
+ test "deny? is false for non-included items" do
+ assert_equal false, @black_list.deny?('first_name')
+ end
+
+ test "sanitize attributes" do
+ original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied', 'admin(1)' => 'denied' }
+ attributes = @black_list.sanitize(original_attributes)
+
+ assert attributes.key?('first_name'), "Allowed key shouldn't be rejected"
+ assert !attributes.key?('admin'), "Denied key should be rejected"
+ assert !attributes.key?('admin(1)'), "Multi-parameter key should be detected"
+ end
+
+end
diff --git a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
new file mode 100644
index 0000000000..d005b638e4
--- /dev/null
+++ b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
@@ -0,0 +1,30 @@
+require "cases/helper"
+
+class PermissionSetTest < ActiveModel::TestCase
+
+ def setup
+ @permission_list = ActiveModel::MassAssignmentSecurity::PermissionSet.new
+ end
+
+ test "+ stringifies added collection values" do
+ symbol_collection = [ :admin ]
+ new_list = @permission_list += symbol_collection
+
+ assert new_list.include?('admin'), "did not add collection to #{@permission_list.inspect}}"
+ end
+
+ test "include? normalizes multi-parameter keys" do
+ multi_param_key = 'admin(1)'
+ new_list = @permission_list += [ 'admin' ]
+
+ assert new_list.include?(multi_param_key), "#{multi_param_key} not found in #{@permission_list.inspect}"
+ end
+
+ test "include? normal keys" do
+ normal_key = 'admin'
+ new_list = @permission_list += [ normal_key ]
+
+ assert new_list.include?(normal_key), "#{normal_key} not found in #{@permission_list.inspect}"
+ end
+
+end
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
new file mode 100644
index 0000000000..367207aab3
--- /dev/null
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -0,0 +1,37 @@
+require "cases/helper"
+require 'logger'
+
+class SanitizerTest < ActiveModel::TestCase
+
+ class SanitizingAuthorizer
+ include ActiveModel::MassAssignmentSecurity::Sanitizer
+
+ attr_accessor :logger
+
+ def deny?(key)
+ [ 'admin' ].include?(key)
+ end
+
+ end
+
+ def setup
+ @sanitizer = SanitizingAuthorizer.new
+ end
+
+ test "sanitize attributes" do
+ original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
+ attributes = @sanitizer.sanitize(original_attributes)
+
+ assert attributes.key?('first_name'), "Allowed key shouldn't be rejected"
+ assert !attributes.key?('admin'), "Denied key should be rejected"
+ end
+
+ test "debug mass assignment removal" do
+ original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
+ log = StringIO.new
+ @sanitizer.logger = Logger.new(log)
+ @sanitizer.sanitize(original_attributes)
+ assert (log.string =~ /admin/), "Should log removed attributes: #{log.string}"
+ end
+
+end
diff --git a/activemodel/test/cases/mass_assignment_security/white_list_test.rb b/activemodel/test/cases/mass_assignment_security/white_list_test.rb
new file mode 100644
index 0000000000..aa3596ad2a
--- /dev/null
+++ b/activemodel/test/cases/mass_assignment_security/white_list_test.rb
@@ -0,0 +1,28 @@
+require "cases/helper"
+
+class WhiteListTest < ActiveModel::TestCase
+
+ def setup
+ @white_list = ActiveModel::MassAssignmentSecurity::WhiteList.new
+ @included_key = 'first_name'
+ @white_list += [ @included_key ]
+ end
+
+ test "deny? is false for included items" do
+ assert_equal false, @white_list.deny?(@included_key)
+ end
+
+ test "deny? is true for non-included items" do
+ assert_equal true, @white_list.deny?('admin')
+ end
+
+ test "sanitize attributes" do
+ original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied', 'admin(1)' => 'denied' }
+ attributes = @white_list.sanitize(original_attributes)
+
+ assert attributes.key?('first_name'), "Allowed key shouldn't be rejected"
+ assert !attributes.key?('admin'), "Denied key should be rejected"
+ assert !attributes.key?('admin(1)'), "Multi-parameter key should be detected"
+ end
+
+end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
new file mode 100644
index 0000000000..0f7a38b0bc
--- /dev/null
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -0,0 +1,52 @@
+require "cases/helper"
+require 'models/mass_assignment_specific'
+
+class MassAssignmentSecurityTest < ActiveModel::TestCase
+
+ def test_attribute_protection
+ user = User.new
+ expected = { "name" => "John Smith", "email" => "john@smith.com" }
+ sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true))
+ assert_equal expected, sanitized
+ end
+
+ def test_attributes_accessible
+ user = Person.new
+ expected = { "name" => "John Smith", "email" => "john@smith.com" }
+ sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true))
+ assert_equal expected, sanitized
+ end
+
+ def test_attributes_protected_by_default
+ firm = Firm.new
+ expected = { }
+ sanitized = firm.sanitize_for_mass_assignment({ "type" => "Client" })
+ assert_equal expected, sanitized
+ end
+
+ def test_mass_assignment_protection_inheritance
+ assert LoosePerson.accessible_attributes.blank?
+ assert_equal Set.new([ 'credit_rating', 'administrator']), LoosePerson.protected_attributes
+
+ assert LooseDescendant.accessible_attributes.blank?
+ assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes
+
+ assert LooseDescendantSecond.accessible_attributes.blank?
+ assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes,
+ 'Running attr_protected twice in one class should merge the protections'
+
+ assert (TightPerson.protected_attributes - TightPerson.attributes_protected_by_default).blank?
+ assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
+
+ assert (TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default).blank?
+ assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes
+ end
+
+ def test_mass_assignment_multiparameter_protector
+ task = Task.new
+ attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
+ sanitized = task.sanitize_for_mass_assignment(attributes)
+ assert_equal sanitized, { }
+ end
+
+end \ No newline at end of file
diff --git a/activemodel/test/models/mass_assignment_specific.rb b/activemodel/test/models/mass_assignment_specific.rb
new file mode 100644
index 0000000000..2a8fe170c2
--- /dev/null
+++ b/activemodel/test/models/mass_assignment_specific.rb
@@ -0,0 +1,57 @@
+class User
+ include ActiveModel::MassAssignmentSecurity
+ attr_protected :admin
+
+ public :sanitize_for_mass_assignment
+end
+
+class Person
+ include ActiveModel::MassAssignmentSecurity
+ attr_accessible :name, :email
+
+ public :sanitize_for_mass_assignment
+end
+
+class Firm
+ include ActiveModel::MassAssignmentSecurity
+
+ public :sanitize_for_mass_assignment
+
+ def self.attributes_protected_by_default
+ ["type"]
+ end
+end
+
+class Task
+ include ActiveModel::MassAssignmentSecurity
+ attr_protected :starting
+
+ public :sanitize_for_mass_assignment
+end
+
+class LoosePerson
+ include ActiveModel::MassAssignmentSecurity
+ attr_protected :credit_rating, :administrator
+end
+
+class LooseDescendant < LoosePerson
+ attr_protected :phone_number
+end
+
+class LooseDescendantSecond< LoosePerson
+ attr_protected :phone_number
+ attr_protected :name
+end
+
+class TightPerson
+ include ActiveModel::MassAssignmentSecurity
+ attr_accessible :name, :address
+
+ def self.attributes_protected_by_default
+ ["mobile_number"]
+ end
+end
+
+class TightDescendant < TightPerson
+ attr_accessible :phone_number
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index f13c250ca4..cbec5789fd 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -282,7 +282,12 @@ module ActiveRecord
end
end
else
- records.first.class.preload_associations(records, through_association)
+ options = {}
+ options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions]
+ options[:order] = reflection.options[:order]
+ options[:conditions] = reflection.options[:conditions]
+ records.first.class.preload_associations(records, through_association, options)
+
records.each do |record|
through_records.concat Array.wrap(record.send(through_association))
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 4caa434fc0..65daa8ffbe 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -763,20 +763,27 @@ module ActiveRecord
# An empty array is returned if none are found.
# [collection<<(object, ...)]
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
+ # Note that this operation instantly fires update sql without waiting for the save or update call on the
+ # parent object.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
# Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>,
# and deleted if they're associated with <tt>:dependent => :delete_all</tt>.
# [collection=objects]
- # Replaces the collections content by deleting and adding objects as appropriate.
+ # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
+ # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
+ # direct.
# [collection_singular_ids]
# Returns an array of the associated objects' ids
# [collection_singular_ids=ids]
- # Replace the collection with the objects identified by the primary keys in +ids+
+ # Replace the collection with the objects identified by the primary keys in +ids+. This
+ # method loads the models and calls <tt>collection=</tt>. See above.
# [collection.clear]
# Removes every object from the collection. This destroys the associated objects if they
# are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the
# database if <tt>:dependent => :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
+ # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
+ # Join models are directly deleted.
# [collection.empty?]
# Returns +true+ if there are no associated objects.
# [collection.size]
@@ -869,9 +876,11 @@ module ActiveRecord
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:through]
- # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
+ # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
- # <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
+ # <tt>has_one</tt> or <tt>has_many</tt> association on the join model. The collection of join models can be managed via the collection
+ # API. For example, new join models are created for newly associated objects, and if some are gone their rows are deleted (directly,
+ # no destroy callbacks are triggered).
# [:source]
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
@@ -1186,6 +1195,8 @@ module ActiveRecord
# [collection<<(object, ...)]
# Adds one or more objects to the collection by creating associations in the join table
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
+ # Note that this operation instantly fires update sql without waiting for the save or update call on the
+ # parent object.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by removing their associations from the join table.
# This does not destroy the objects.
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index ddf4ce4058..615b7d2719 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -253,9 +253,10 @@ module ActiveRecord
# See destroy for more info.
def destroy_all
load_target
- destroy(@target)
- reset_target!
- reset_named_scopes_cache!
+ destroy(@target).tap do
+ reset_target!
+ reset_named_scopes_cache!
+ end
end
def create(attrs = {})
@@ -393,7 +394,12 @@ module ActiveRecord
@target = find_target.map do |f|
i = @target.index(f)
t = @target.delete_at(i) if i
- (t && t.changed?) ? t : f
+ if t && t.changed?
+ t
+ else
+ f.mark_for_destruction if t && t.marked_for_destruction?
+ f
+ end
end + @target
else
@target = find_target
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c8795e4496..c78060c956 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -24,7 +24,7 @@ require 'active_record/errors'
require 'active_record/log_subscriber'
module ActiveRecord #:nodoc:
- # = Active Record
+ # = Active Record
#
# Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
# which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
@@ -476,112 +476,16 @@ module ActiveRecord #:nodoc:
connection.select_value(sql, "#{name} Count").to_i
end
- # Attributes named in this macro are protected from mass-assignment,
- # such as <tt>new(attributes)</tt>,
- # <tt>update_attributes(attributes)</tt>, or
- # <tt>attributes=(attributes)</tt>.
- #
- # Mass-assignment to these attributes will simply be ignored, to assign
- # to them you can use direct writer methods. This is meant to protect
- # sensitive attributes from being overwritten by malicious users
- # tampering with URLs or forms.
- #
- # class Customer < ActiveRecord::Base
- # attr_protected :credit_rating
- # end
- #
- # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
- # customer.credit_rating # => nil
- # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
- # customer.credit_rating # => nil
- #
- # customer.credit_rating = "Average"
- # customer.credit_rating # => "Average"
- #
- # To start from an all-closed default and enable attributes as needed,
- # have a look at +attr_accessible+.
- #
- # If the access logic of your application is richer you can use <tt>Hash#except</tt>
- # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
- # passed to Active Record.
- #
- # For example, it could be the case that the list of protected attributes
- # for a given model depends on the role of the user:
- #
- # # Assumes plan_id is not protected because it depends on the role.
- # params[:account] = params[:account].except(:plan_id) unless admin?
- # @account.update_attributes(params[:account])
- #
- # Note that +attr_protected+ is still applied to the received hash. Thus,
- # with this technique you can at most _extend_ the list of protected
- # attributes for a particular mass-assignment call.
- def attr_protected(*attributes)
- write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || []))
+ # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
+ def attr_readonly(*attributes)
+ write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
end
- # Returns an array of all the attributes that have been protected from mass-assignment.
- def protected_attributes # :nodoc:
- read_inheritable_attribute(:attr_protected)
+ # Returns an array of all the attributes that have been specified as readonly.
+ def readonly_attributes
+ read_inheritable_attribute(:attr_readonly) || []
end
- # Specifies a white list of model attributes that can be set via
- # mass-assignment, such as <tt>new(attributes)</tt>,
- # <tt>update_attributes(attributes)</tt>, or
- # <tt>attributes=(attributes)</tt>
- #
- # This is the opposite of the +attr_protected+ macro: Mass-assignment
- # will only set attributes in this list, to assign to the rest of
- # attributes you can use direct writer methods. This is meant to protect
- # sensitive attributes from being overwritten by malicious users
- # tampering with URLs or forms. If you'd rather start from an all-open
- # default and restrict attributes as needed, have a look at
- # +attr_protected+.
- #
- # class Customer < ActiveRecord::Base
- # attr_accessible :name, :nickname
- # end
- #
- # customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
- # customer.credit_rating # => nil
- # customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
- # customer.credit_rating # => nil
- #
- # customer.credit_rating = "Average"
- # customer.credit_rating # => "Average"
- #
- # If the access logic of your application is richer you can use <tt>Hash#except</tt>
- # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
- # passed to Active Record.
- #
- # For example, it could be the case that the list of accessible attributes
- # for a given model depends on the role of the user:
- #
- # # Assumes plan_id is accessible because it depends on the role.
- # params[:account] = params[:account].except(:plan_id) unless admin?
- # @account.update_attributes(params[:account])
- #
- # Note that +attr_accessible+ is still applied to the received hash. Thus,
- # with this technique you can at most _narrow_ the list of accessible
- # attributes for a particular mass-assignment call.
- def attr_accessible(*attributes)
- write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
- end
-
- # Returns an array of all the attributes that have been made accessible to mass-assignment.
- def accessible_attributes # :nodoc:
- read_inheritable_attribute(:attr_accessible)
- end
-
- # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
- def attr_readonly(*attributes)
- write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
- end
-
- # Returns an array of all the attributes that have been specified as readonly.
- def readonly_attributes
- read_inheritable_attribute(:attr_readonly) || []
- end
-
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
# then specify the name of that attribute using this method and it will be handled automatically.
# The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
@@ -869,6 +773,9 @@ module ActiveRecord #:nodoc:
# Returns the base AR subclass that this class descends from. If A
# extends AR::Base, A.base_class will return A. If B descends from A
# through some arbitrarily deep hierarchy, B.base_class will return A.
+ #
+ # If B < A and C < B and if A is an abstract_class then both B.base_class
+ # and C.base_class would return B as the answer since A is an abstract_class.
def base_class
class_of_active_record_descendant(self)
end
@@ -876,8 +783,7 @@ module ActiveRecord #:nodoc:
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
attr_accessor :abstract_class
- # Returns whether this class is a base AR class. If A is a base class and
- # B descends from A, then B.base_class will return B.
+ # Returns whether this class is an abstract class or not.
def abstract_class?
defined?(@abstract_class) && @abstract_class == true
end
@@ -896,11 +802,6 @@ module ActiveRecord #:nodoc:
store_full_sti_class ? name : name.demodulize
end
- def relation
- @relation ||= Relation.new(self, arel_table)
- finder_needs_type_condition? ? @relation.where(type_condition) : @relation
- end
-
def arel_table
@arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
end
@@ -941,6 +842,12 @@ module ActiveRecord #:nodoc:
end
private
+
+ def relation #:nodoc:
+ @relation ||= Relation.new(self, arel_table)
+ finder_needs_type_condition? ? @relation.where(type_condition) : @relation
+ 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.
@@ -1245,11 +1152,6 @@ MSG
end
end
- # Returns the name of the class descending directly from Active Record in the inheritance hierarchy.
- def class_name_of_active_record_descendant(klass) #:nodoc:
- klass.base_class.name
- end
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a WHERE clause.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
@@ -1569,11 +1471,11 @@ MSG
# user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
# user.is_admin? # => true
def attributes=(new_attributes, guard_protected_attributes = true)
- return if new_attributes.nil?
+ return unless new_attributes.is_a? Hash
attributes = new_attributes.stringify_keys
multi_parameter_attributes = []
- attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
+ attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
attributes.each do |k, v|
if k.include?("(")
@@ -1713,46 +1615,10 @@ MSG
end
end
- def remove_attributes_protected_from_mass_assignment(attributes)
- safe_attributes =
- if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
- attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
- elsif self.class.protected_attributes.nil?
- attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
- elsif self.class.accessible_attributes.nil?
- attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
- else
- raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
- end
-
- removed_attributes = attributes.keys - safe_attributes.keys
-
- if removed_attributes.any?
- log_protected_attribute_removal(removed_attributes)
- end
-
- safe_attributes
- end
-
- # Removes attributes which have been marked as readonly.
- def remove_readonly_attributes(attributes)
- unless self.class.readonly_attributes.nil?
- attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) }
- else
- attributes
- end
- end
-
- def log_protected_attribute_removal(*attributes)
- if logger
- logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
- end
- end
-
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
- def attributes_protected_by_default
- default = [ self.class.primary_key, self.class.inheritance_column ]
- default << 'id' unless self.class.primary_key.eql? 'id'
+ def self.attributes_protected_by_default
+ default = [ primary_key, inheritance_column ]
+ default << 'id' unless primary_key.eql? 'id'
default
end
@@ -1917,6 +1783,7 @@ MSG
include AttributeMethods::PrimaryKey
include AttributeMethods::TimeZoneConversion
include AttributeMethods::Dirty
+ include ActiveModel::MassAssignmentSecurity
include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 76b65bf219..ffc3847a31 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -103,10 +103,15 @@ module ActiveRecord
# The +options+ hash can include the following keys:
# [<tt>:id</tt>]
# Whether to automatically add a primary key column. Defaults to true.
- # Join tables for +has_and_belongs_to_many+ should set <tt>:id => false</tt>.
+ # Join tables for +has_and_belongs_to_many+ should set it to false.
# [<tt>:primary_key</tt>]
# The name of the primary key, if one is to be added automatically.
- # Defaults to +id+.
+ # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
+ #
+ # Also note that this just sets the primary key in the table. You additionally
+ # need to configure the primary key in the model via the +set_primary_key+ macro.
+ # Models do NOT auto-detect the primary key from their table definition.
+ #
# [<tt>:options</tt>]
# Any extra options you want appended to the table definition.
# [<tt>:temporary</tt>]
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 0d9a86a1ea..e5e92f2b1c 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -4,7 +4,17 @@ module ActiveRecord
class Base
# sqlite3 adapter reuses sqlite_connection.
def self.sqlite3_connection(config) # :nodoc:
- parse_sqlite_config!(config)
+ # Require database.
+ unless config[:database]
+ raise ArgumentError, "No database file specified. Missing argument: database"
+ end
+
+ # Allow database path relative to Rails.root, but only if
+ # the database path is not the special path that tells
+ # Sqlite to build a database only in memory.
+ if defined?(Rails.root) && ':memory:' != config[:database]
+ config[:database] = File.expand_path(config[:database], Rails.root)
+ end
unless 'sqlite3' == config[:adapter]
raise ArgumentError, 'adapter name should be "sqlite3"'
@@ -16,8 +26,7 @@ module ActiveRecord
db = SQLite3::Database.new(
config[:database],
- :results_as_hash => true,
- :type_translation => false
+ :results_as_hash => true
)
db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 1927585c49..117cf447df 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -2,25 +2,6 @@ require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/kernel/requires'
module ActiveRecord
- class Base
- class << self
- private
- def parse_sqlite_config!(config)
- # Require database.
- unless config[:database]
- raise ArgumentError, "No database file specified. Missing argument: database"
- end
-
- # Allow database path relative to Rails.root, but only if
- # the database path is not the special path that tells
- # Sqlite to build a database only in memory.
- if defined?(Rails.root) && ':memory:' != config[:database]
- config[:database] = File.expand_path(config[:database], Rails.root)
- end
- end
- end
- end
-
module ConnectionAdapters #:nodoc:
class SQLiteColumn < Column #:nodoc:
class << self
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 999322129a..237cd56683 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -17,9 +17,18 @@ module ActiveRecord
def reset_counters(id, *counters)
object = find(id)
counters.each do |association|
- child_class = reflect_on_association(association.to_sym).klass
- belongs_name = self.name.demodulize.underscore.to_sym
- counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column
+ has_many_association = reflect_on_association(association.to_sym)
+
+ expected_name = if has_many_association.options[:as]
+ has_many_association.options[:as].to_s.classify
+ else
+ self.name
+ end
+
+ child_class = has_many_association.klass
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
+ reflection = belongs_to.find { |e| e.class_name == expected_name }
+ counter_name = reflection.counter_cache_column
self.unscoped.where(arel_table[self.primary_key].eq(object.id)).arel.update({
arel_table[counter_name] => object.send(association).count
@@ -103,4 +112,4 @@ module ActiveRecord
update_counters(id, counter_name => -1)
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index c010dac64e..849ec9c884 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -29,7 +29,7 @@ module ActiveRecord
if options.present?
scoped.apply_finder_options(options)
else
- current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
+ current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone
end
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 5f80bd86df..d2ed643f35 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -94,7 +94,7 @@ module ActiveRecord
def initialize
super
- observed_subclasses.each { |klass| add_observer!(klass) }
+ observed_descendants.each { |klass| add_observer!(klass) }
end
def self.method_added(method)
@@ -108,8 +108,8 @@ module ActiveRecord
protected
- def observed_subclasses
- observed_classes.sum([]) { |klass| klass.send(:descendants) }
+ def observed_descendants
+ observed_classes.sum([]) { |klass| klass.descendants }
end
def observe_callbacks?
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 50166c4385..828a8b41b6 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -102,12 +102,21 @@ module ActiveRecord
became
end
- # Updates a single attribute and saves the record without going through the normal validation procedure.
- # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
- # in Base is replaced with this when the validations module is mixed in, which it is by default.
+ # Updates a single attribute and saves the record without going through the normal validation procedure
+ # or callbacks. This is especially useful for boolean flags on existing records.
def update_attribute(name, value)
send("#{name}=", value)
- save(:validate => false)
+ hash = { name => read_attribute(name) }
+
+ if record_update_timestamps
+ timestamp_attributes_for_update_in_model.each do |column|
+ hash[column] = read_attribute(column)
+ end
+ end
+
+ @changed_attributes.delete(name.to_s)
+ primary_key = self.class.primary_key
+ self.class.update_all(hash, { primary_key => self[primary_key] }) == 1
end
# Updates all the attributes from the passed-in Hash and saves the record.
@@ -234,4 +243,4 @@ module ActiveRecord
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 7882f05d07..5024787c3c 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -1,7 +1,7 @@
namespace :db do
task :load_config => :rails_env do
require 'active_record'
- ActiveRecord::Base.configurations = Rails::Application.config.database_configuration
+ ActiveRecord::Base.configurations = Rails.application.config.database_configuration
end
namespace :create do
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index bc708b573f..d9fc1b4940 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -213,8 +213,7 @@ module ActiveRecord
if conditions
where(conditions).destroy_all
else
- to_a.each {|object| object.destroy}
- reset
+ to_a.each {|object| object.destroy }.tap { reset }
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index f39951e16c..3bf4c5bdd1 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -339,7 +339,7 @@ module ActiveRecord
end
def using_limitable_reflections?(reflections)
- reflections.collect(&:collection?).length.zero?
+ reflections.none?(&:collection?)
end
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index ffd12d2082..1075a60f07 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -35,8 +35,7 @@ module ActiveRecord
if attribute
write_attribute(attribute, current_time)
else
- write_attribute('updated_at', current_time) if respond_to?(:updated_at)
- write_attribute('updated_on', current_time) if respond_to?(:updated_on)
+ timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) }
end
save!
@@ -50,26 +49,36 @@ module ActiveRecord
write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
- write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
- write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
+ timestamp_attributes_for_update_in_model.each do |column|
+ write_attribute(column.to_s, current_time) if self.send(column).nil?
+ end
end
super
end
def update(*args) #:nodoc:
+ record_update_timestamps
+ super
+ end
+
+ def record_update_timestamps
if record_timestamps && (!partial_updates? || changed?)
current_time = current_time_from_proper_timezone
-
- write_attribute('updated_at', current_time) if respond_to?(:updated_at)
- write_attribute('updated_on', current_time) if respond_to?(:updated_on)
+ timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) }
+ true
+ else
+ false
end
+ end
- super
+ def timestamp_attributes_for_update_in_model #:nodoc:
+ [:updated_at, :updated_on].select { |elem| respond_to?(elem) }
end
- def current_time_from_proper_timezone
+ def current_time_from_proper_timezone #:nodoc:
self.class.default_timezone == :utc ? Time.now.utc : Time.now
end
end
-end \ No newline at end of file
+end
+
diff --git a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
new file mode 100644
index 0000000000..2505372b7e
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
@@ -0,0 +1,120 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class SQLiteAdapterTest < ActiveRecord::TestCase
+ def setup
+ @ctx = Base.sqlite3_connection :database => ':memory:',
+ :adapter => 'sqlite3',
+ :timeout => nil
+ @ctx.execute <<-eosql
+ CREATE TABLE items (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ )
+ eosql
+ end
+
+ def test_execute
+ @ctx.execute "INSERT INTO items (number) VALUES (10)"
+ records = @ctx.execute "SELECT * FROM items"
+ assert_equal 1, records.length
+
+ record = records.first
+ assert_equal 10, record['number']
+ assert_equal 1, record['id']
+ end
+
+ def test_quote_string
+ assert_equal "''", @ctx.quote_string("'")
+ end
+
+ def test_insert_sql
+ 2.times do |i|
+ rv = @ctx.insert_sql "INSERT INTO items (number) VALUES (#{i})"
+ assert_equal(i + 1, rv)
+ end
+
+ records = @ctx.execute "SELECT * FROM items"
+ assert_equal 2, records.length
+ end
+
+ def test_insert_sql_logged
+ sql = "INSERT INTO items (number) VALUES (10)"
+ name = "foo"
+
+ assert_logged([[sql, name]]) do
+ @ctx.insert_sql sql, name
+ end
+ end
+
+ def test_insert_id_value_returned
+ sql = "INSERT INTO items (number) VALUES (10)"
+ idval = 'vuvuzela'
+ id = @ctx.insert_sql sql, nil, nil, idval
+ assert_equal idval, id
+ end
+
+ def test_select_rows
+ 2.times do |i|
+ @ctx.create "INSERT INTO items (number) VALUES (#{i})"
+ end
+ rows = @ctx.select_rows 'select number, id from items'
+ assert_equal [[0, 1], [1, 2]], rows
+ end
+
+ def test_select_rows_logged
+ sql = "select * from items"
+ name = "foo"
+
+ assert_logged([[sql, name]]) do
+ @ctx.select_rows sql, name
+ end
+ end
+
+ def test_transaction
+ count_sql = 'select count(*) from items'
+
+ @ctx.begin_db_transaction
+ @ctx.create "INSERT INTO items (number) VALUES (10)"
+
+ assert_equal 1, @ctx.select_rows(count_sql).first.first
+ @ctx.rollback_db_transaction
+ assert_equal 0, @ctx.select_rows(count_sql).first.first
+ end
+
+ def test_tables
+ assert_equal %w{ items }, @ctx.tables
+
+ @ctx.execute <<-eosql
+ CREATE TABLE people (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ )
+ eosql
+ assert_equal %w{ items people }.sort, @ctx.tables.sort
+ end
+
+ def test_tables_logs_name
+ name = "hello"
+ assert_logged [[name]] do
+ @ctx.tables(name)
+ assert_not_nil @ctx.logged.first.shift
+ end
+ end
+
+ def assert_logged logs
+ @ctx.extend(Module.new {
+ attr_reader :logged
+ def log sql, name
+ @logged ||= []
+ @logged << [sql, name]
+ yield
+ end
+ })
+ yield
+ assert_equal logs, @ctx.logged
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 445e6889c0..40859d425f 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -357,6 +357,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_eager_with_has_many_through_with_conditions_join_model_with_include
+ post_tags = Post.find(posts(:welcome).id).misc_tags
+ eager_post_tags = Post.find(1, :include => :misc_tags).misc_tags
+ assert_equal post_tags, eager_post_tags
+ end
+
def test_eager_with_has_many_and_limit
posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2)
assert_equal 2, posts.size
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 004d0156e1..b11969a841 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -684,8 +684,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_scoped_grouped
- assert_equal 4, categories(:general).posts_gruoped_by_title.size
- assert_equal 1, categories(:technology).posts_gruoped_by_title.size
+ assert_equal 4, categories(:general).posts_grouped_by_title.size
+ assert_equal 1, categories(:technology).posts_grouped_by_title.size
end
def test_find_scoped_grouped_having
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 5e3ba778f3..a52cedd8c2 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -817,8 +817,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroy_all
force_signal37_to_load_all_clients_of_firm
- assert !companies(:first_firm).clients_of_firm.empty?, "37signals has clients after load"
- companies(:first_firm).clients_of_firm.destroy_all
+ clients = companies(:first_firm).clients_of_firm.to_a
+ assert !clients.empty?, "37signals has clients after load"
+ destroyed = companies(:first_firm).clients_of_firm.destroy_all
+ assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
+ assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 9aef3eb374..178c57435b 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -65,10 +65,6 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal clubs(:moustache_club), @member.sponsor_club
end
- def has_one_through_to_has_many
- assert_equal 2, @member.fellow_members.size
- end
-
def test_has_one_through_eager_loading
members = assert_queries(3) do #base table, through table, clubs table
Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"])
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index d99fb44f01..4ae776c35a 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -65,6 +65,16 @@ class AssociationsTest < ActiveRecord::TestCase
assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
end
+ def test_using_limitable_reflections_helper
+ using_limitable_reflections = lambda { |reflections| Tagging.scoped.send :using_limitable_reflections?, reflections }
+ belongs_to_reflections = [Tagging.reflect_on_association(:tag), Tagging.reflect_on_association(:super_tag)]
+ has_many_reflections = [Tag.reflect_on_association(:taggings), Developer.reflect_on_association(:projects)]
+ mixed_reflections = (belongs_to_reflections + has_many_reflections).uniq
+ assert using_limitable_reflections.call(belongs_to_reflections), "Belong to associations are limitable"
+ assert !using_limitable_reflections.call(has_many_reflections), "All has many style associations are not limitable"
+ assert !using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass"
+ end
+
def test_force_reload_is_uncached
firm = Firm.create!("name" => "A New Firm, Inc")
client = Client.create!("name" => "TheClient.com", :firm => firm)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index ba7db838ca..a4cf5120e1 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -17,6 +17,7 @@ require 'models/comment'
require 'models/minimalistic'
require 'models/warehouse_thing'
require 'models/parrot'
+require 'models/loose_person'
require 'rexml/document'
require 'active_support/core_ext/exception'
@@ -37,46 +38,12 @@ class Computer < ActiveRecord::Base; end
class NonExistentTable < ActiveRecord::Base; end
class TestOracleDefault < ActiveRecord::Base; end
-class LoosePerson < ActiveRecord::Base
- self.table_name = 'people'
- self.abstract_class = true
- attr_protected :credit_rating, :administrator
-end
-
-class LooseDescendant < LoosePerson
- attr_protected :phone_number
-end
-
-class LooseDescendantSecond< LoosePerson
- attr_protected :phone_number
- attr_protected :name
-end
-
-class TightPerson < ActiveRecord::Base
- self.table_name = 'people'
- attr_accessible :name, :address
-end
-
-class TightDescendant < TightPerson
- attr_accessible :phone_number
-end
-
class ReadonlyTitlePost < Post
attr_readonly :title
end
class Booleantest < ActiveRecord::Base; end
-class Task < ActiveRecord::Base
- attr_protected :starting
-end
-
-class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base
- self.table_name = 'topics'
- attr_accessible :author_name
- attr_protected :content
-end
-
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
@@ -94,6 +61,13 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
end
+ def test_set_attributes_without_hash
+ topic = Topic.new
+ assert_nothing_raised do
+ topic.attributes = ''
+ end
+ end
+
def test_integers_as_nil
test = AutoId.create('value' => '')
assert_nil AutoId.find(test.id).value
@@ -684,16 +658,24 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_destroy_all
- original_count = Topic.count
- topics_by_mary = Topic.count(:conditions => mary = "author_name = 'Mary'")
-
- Topic.destroy_all mary
- assert_equal original_count - topics_by_mary, Topic.count
+ conditions = "author_name = 'Mary'"
+ topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
+ assert ! topics_by_mary.empty?
+
+ assert_difference('Topic.count', -topics_by_mary.size) do
+ destroyed = Topic.destroy_all(conditions).sort_by(&:id)
+ assert_equal topics_by_mary, destroyed
+ assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen"
+ end
end
def test_destroy_many
+ clients = Client.find([2, 3], :order => 'id')
+
assert_difference('Client.count', -2) do
- Client.destroy([2, 3])
+ destroyed = Client.destroy([2, 3]).sort_by(&:id)
+ assert_equal clients, destroyed
+ assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
end
end
@@ -918,6 +900,46 @@ class BasicsTest < ActiveRecord::TestCase
assert !Topic.find(1).approved?
end
+ def test_update_attribute_with_one_changed_and_one_updated
+ t = Topic.order('id').limit(1).first
+ title, author_name = t.title, t.author_name
+ t.author_name = 'John'
+ t.update_attribute(:title, 'super_title')
+ assert_equal 'John', t.author_name
+ assert_equal 'super_title', t.title
+ assert t.changed?, "topic should have changed"
+ assert t.author_name_changed?, "author_name should have changed"
+ assert !t.title_changed?, "title should not have changed"
+ assert_nil t.title_change, 'title change should be nil'
+ assert_equal ['author_name'], t.changed
+
+ t.reload
+ assert_equal 'David', t.author_name
+ assert_equal 'super_title', t.title
+ end
+
+ def test_update_attribute_with_one_updated
+ t = Topic.first
+ title = t.title
+ t.update_attribute(:title, 'super_title')
+ assert_equal 'super_title', t.title
+ assert !t.changed?, "topic should not have changed"
+ assert !t.title_changed?, "title should not have changed"
+ assert_nil t.title_change, 'title change should be nil'
+
+ t.reload
+ assert_equal 'super_title', t.title
+ end
+
+ def test_update_attribute_for_udpated_at_on
+ developer = Developer.find(1)
+ updated_at = developer.updated_at
+ developer.update_attribute(:salary, 80001)
+ assert_not_equal updated_at, developer.updated_at
+ developer.reload
+ assert_not_equal updated_at, developer.updated_at
+ end
+
def test_update_attributes
topic = Topic.find(1)
assert !topic.approved?
@@ -955,88 +977,6 @@ class BasicsTest < ActiveRecord::TestCase
Reply.reset_callbacks(:validate)
end
- def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used
- topic = TopicWithProtectedContentAndAccessibleAuthorName.new
- assert_raise(RuntimeError) { topic.attributes = { "author_name" => "me" } }
- assert_raise(RuntimeError) { topic.attributes = { "content" => "stuff" } }
- end
-
- def test_mass_assignment_protection
- firm = Firm.new
- firm.attributes = { "name" => "Next Angle", "rating" => 5 }
- assert_equal 1, firm.rating
- end
-
- def test_mass_assignment_protection_against_class_attribute_writers
- [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names,
- :default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method|
- assert_respond_to Task, method
- assert_respond_to Task, "#{method}="
- assert_respond_to Task.new, method
- assert !Task.new.respond_to?("#{method}=")
- end
- end
-
- def test_customized_primary_key_remains_protected
- subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
- assert_nil subscriber.id
-
- keyboard = Keyboard.new(:key_number => 9, :name => 'nice try')
- assert_nil keyboard.id
- end
-
- def test_customized_primary_key_remains_protected_when_referred_to_as_id
- subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
- assert_nil subscriber.id
-
- keyboard = Keyboard.new(:id => 9, :name => 'nice try')
- assert_nil keyboard.id
- end
-
- def test_mass_assigning_invalid_attribute
- firm = Firm.new
-
- assert_raise(ActiveRecord::UnknownAttributeError) do
- firm.attributes = { "id" => 5, "type" => "Client", "i_dont_even_exist" => 20 }
- end
- end
-
- def test_mass_assignment_protection_on_defaults
- firm = Firm.new
- firm.attributes = { "id" => 5, "type" => "Client" }
- assert_nil firm.id
- assert_equal "Firm", firm[:type]
- end
-
- def test_mass_assignment_accessible
- reply = Reply.new("title" => "hello", "content" => "world", "approved" => true)
- reply.save
-
- assert reply.approved?
-
- reply.approved = false
- reply.save
-
- assert !reply.approved?
- end
-
- def test_mass_assignment_protection_inheritance
- assert_nil LoosePerson.accessible_attributes
- assert_equal Set.new([ 'credit_rating', 'administrator' ]), LoosePerson.protected_attributes
-
- assert_nil LooseDescendant.accessible_attributes
- assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number' ]), LooseDescendant.protected_attributes
-
- assert_nil LooseDescendantSecond.accessible_attributes
- assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name' ]), LooseDescendantSecond.protected_attributes, 'Running attr_protected twice in one class should merge the protections'
-
- assert_nil TightPerson.protected_attributes
- assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
-
- assert_nil TightDescendant.protected_attributes
- assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes
- end
-
def test_readonly_attributes
assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes
@@ -1239,15 +1179,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
- def test_multiparameter_mass_assignment_protector
- task = Task.new
- time = Time.mktime(2000, 1, 1, 1)
- task.starting = time
- attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
- task.attributes = attributes
- assert_equal time, task.starting
- end
-
def test_multiparameter_assignment_of_aggregation
customer = Customer.new
address = Address.new("The Street", "The City", "The Country")
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 377de168b9..137236255d 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -1,17 +1,20 @@
require 'cases/helper'
require 'models/topic'
+require 'models/car'
+require 'models/wheel'
+require 'models/engine'
require 'models/reply'
require 'models/category'
require 'models/categorization'
class CounterCacheTest < ActiveRecord::TestCase
- fixtures :topics, :categories, :categorizations
+ fixtures :topics, :categories, :categorizations, :cars
- class SpecialTopic < ::Topic
+ class ::SpecialTopic < ::Topic
has_many :special_replies, :foreign_key => 'parent_id'
end
- class SpecialReply < ::Reply
+ class ::SpecialReply < ::Reply
belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count'
end
@@ -58,6 +61,16 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test "reset counter should with belongs_to which has class_name" do
+ car = cars(:honda)
+ assert_nothing_raised do
+ Car.reset_counters(car.id, :engines)
+ end
+ assert_nothing_raised do
+ Car.reset_counters(car.id, :wheels)
+ end
+ end
+
test "update counter with initial null value" do
category = categories(:general)
assert_equal 2, category.categorizations.count
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 75f7453aa9..837386ed24 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -475,10 +475,9 @@ class DirtyTest < ActiveRecord::TestCase
pirate = Pirate.find_by_catchphrase("Ahoy!")
pirate.update_attribute(:catchphrase, "Ninjas suck!")
- assert_equal 2, pirate.previous_changes.size
- assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes['catchphrase']
- assert_not_nil pirate.previous_changes['updated_on'][0]
- assert_not_nil pirate.previous_changes['updated_on'][1]
+ assert_equal 0, pirate.previous_changes.size
+ assert_nil pirate.previous_changes['catchphrase']
+ assert_nil pirate.previous_changes['updated_on']
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
new file mode 100644
index 0000000000..025ec1d3fa
--- /dev/null
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -0,0 +1,43 @@
+require "cases/helper"
+require 'models/company'
+require 'models/subscriber'
+require 'models/keyboard'
+require 'models/task'
+
+class MassAssignmentSecurityTest < ActiveRecord::TestCase
+
+ def test_customized_primary_key_remains_protected
+ subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
+ assert_nil subscriber.id
+
+ keyboard = Keyboard.new(:key_number => 9, :name => 'nice try')
+ assert_nil keyboard.id
+ end
+
+ def test_customized_primary_key_remains_protected_when_referred_to_as_id
+ subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
+ assert_nil subscriber.id
+
+ keyboard = Keyboard.new(:id => 9, :name => 'nice try')
+ assert_nil keyboard.id
+ end
+
+ def test_mass_assigning_invalid_attribute
+ firm = Firm.new
+
+ assert_raise(ActiveRecord::UnknownAttributeError) do
+ firm.attributes = { "id" => 5, "type" => "Client", "i_dont_even_exist" => 20 }
+ end
+ end
+
+ def test_protection_against_class_attribute_writers
+ [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names,
+ :default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method|
+ assert_respond_to Task, method
+ assert_respond_to Task, "#{method}="
+ assert_respond_to Task.new, method
+ assert !Task.new.respond_to?("#{method}=")
+ end
+ end
+
+end \ No newline at end of file
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 4ce9bdb46d..2c3fc46831 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1358,6 +1358,20 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid")
assert_equal(3, ActiveRecord::Migrator.current_version)
end
+
+ def test_get_all_versions
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
+ assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions)
+
+ ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
+ assert_equal([1,2], ActiveRecord::Migrator.get_all_versions)
+
+ ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
+ assert_equal([1], ActiveRecord::Migrator.get_all_versions)
+
+ ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
+ assert_equal([], ActiveRecord::Migrator.get_all_versions)
+ end
def test_schema_migrations_table_name
ActiveRecord::Base.table_name_prefix = "prefix_"
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 3c797076e0..c9ea0d8c40 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -195,7 +195,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
[1, '1', true, 'true'].each do |truth|
@pirate.reload.create_ship(:name => 'Mister Pablo')
assert_difference('Ship.count', -1) do
- @pirate.update_attribute(:ship_attributes, { :id => @pirate.ship.id, :_destroy => truth })
+ @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => truth })
end
end
end
@@ -203,7 +203,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
[nil, '0', 0, 'false', false].each do |not_truth|
assert_no_difference('Ship.count') do
- @pirate.update_attribute(:ship_attributes, { :id => @pirate.ship.id, :_destroy => not_truth })
+ @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => not_truth })
end
end
end
@@ -212,7 +212,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
assert_no_difference('Ship.count') do
- @pirate.update_attribute(:ship_attributes, { :id => @pirate.ship.id, :_destroy => '1' })
+ @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => '1' })
end
Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
@@ -247,13 +247,13 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_accept_update_only_option
- @pirate.update_attribute(:update_only_ship_attributes, { :id => @pirate.ship.id, :name => 'Mayflower' })
+ @pirate.update_attributes(:update_only_ship_attributes => { :id => @pirate.ship.id, :name => 'Mayflower' })
end
def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
@ship.delete
assert_difference('Ship.count', 1) do
- @pirate.reload.update_attribute(:update_only_ship_attributes, { :name => 'Mayflower' })
+ @pirate.reload.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' })
end
end
@@ -353,7 +353,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
[1, '1', true, 'true'].each do |truth|
@ship.reload.create_pirate(:catchphrase => 'Arr')
assert_difference('Pirate.count', -1) do
- @ship.update_attribute(:pirate_attributes, { :id => @ship.pirate.id, :_destroy => truth })
+ @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => truth })
end
end
end
@@ -361,7 +361,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
[nil, '0', 0, 'false', false].each do |not_truth|
assert_no_difference('Pirate.count') do
- @ship.update_attribute(:pirate_attributes, { :id => @ship.pirate.id, :_destroy => not_truth })
+ @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => not_truth })
end
end
end
@@ -370,7 +370,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
assert_no_difference('Pirate.count') do
- @ship.update_attribute(:pirate_attributes, { :id => @ship.pirate.id, :_destroy => '1' })
+ @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => '1' })
end
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
@@ -398,7 +398,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
@pirate.delete
assert_difference('Pirate.count', 1) do
- @ship.reload.update_attribute(:update_only_pirate_attributes, { :catchphrase => 'Arr' })
+ @ship.reload.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr' })
end
end
@@ -489,6 +489,12 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
end
+ def test_should_not_remove_scheduled_destroys_when_loading_association
+ @pirate.reload
+ @pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }])
+ assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction?
+ end
+
def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
@child_1.stubs(:id).returns('ABC1X')
@child_2.stubs(:id).returns('ABC2X')
diff --git a/activerecord/test/fixtures/cars.yml b/activerecord/test/fixtures/cars.yml
new file mode 100644
index 0000000000..23c98e8144
--- /dev/null
+++ b/activerecord/test/fixtures/cars.yml
@@ -0,0 +1,4 @@
+honda:
+ id: 1
+ name: honda
+ engines_count: 0
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
new file mode 100644
index 0000000000..1101180a67
--- /dev/null
+++ b/activerecord/test/models/car.rb
@@ -0,0 +1,4 @@
+class Car < ActiveRecord::Base
+ has_many :engines
+ has_many :wheels, :as => :wheelable
+end
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index 5efce6aaa6..48415846dd 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -15,7 +15,7 @@ class Category < ActiveRecord::Base
:conditions => { :title => 'Yet Another Testing Title' }
has_and_belongs_to_many :popular_grouped_posts, :class_name => "Post", :group => "posts.type", :having => "sum(comments.post_id) > 2", :include => :comments
- has_and_belongs_to_many :posts_gruoped_by_title, :class_name => "Post", :group => "title", :select => "title"
+ has_and_belongs_to_many :posts_grouped_by_title, :class_name => "Post", :group => "title", :select => "title"
def self.what_are_you
'a category...'
diff --git a/activerecord/test/models/engine.rb b/activerecord/test/models/engine.rb
new file mode 100644
index 0000000000..751c3f02d1
--- /dev/null
+++ b/activerecord/test/models/engine.rb
@@ -0,0 +1,3 @@
+class Engine < ActiveRecord::Base
+ belongs_to :my_car, :class_name => 'Car', :foreign_key => 'car_id', :counter_cache => :engines_count
+end
diff --git a/activerecord/test/models/loose_person.rb b/activerecord/test/models/loose_person.rb
new file mode 100644
index 0000000000..256c281d0d
--- /dev/null
+++ b/activerecord/test/models/loose_person.rb
@@ -0,0 +1,24 @@
+class LoosePerson < ActiveRecord::Base
+ self.table_name = 'people'
+ self.abstract_class = true
+
+ attr_protected :credit_rating, :administrator
+end
+
+class LooseDescendant < LoosePerson
+ attr_protected :phone_number
+end
+
+class LooseDescendantSecond< LoosePerson
+ attr_protected :phone_number
+ attr_protected :name
+end
+
+class TightPerson < ActiveRecord::Base
+ self.table_name = 'people'
+ attr_accessible :name, :address
+end
+
+class TightDescendant < TightPerson
+ attr_accessible :phone_number
+end \ No newline at end of file
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index dd06822cfd..6c7b93be87 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -58,6 +58,7 @@ class Post < ActiveRecord::Base
end
end
+ has_many :misc_tags, :through => :taggings, :source => :tag, :conditions => "tags.name = 'Misc'"
has_many :funky_tags, :through => :taggings, :source => :tag
has_many :super_tags, :through => :taggings
has_one :tagging, :as => :taggable
diff --git a/activerecord/test/models/wheel.rb b/activerecord/test/models/wheel.rb
new file mode 100644
index 0000000000..26868bce5e
--- /dev/null
+++ b/activerecord/test/models/wheel.rb
@@ -0,0 +1,3 @@
+class Wheel < ActiveRecord::Base
+ belongs_to :wheelable, :polymorphic => true, :counter_cache => true
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index b212e7cff2..bea351b95a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -82,6 +82,12 @@ ActiveRecord::Schema.define do
t.string :name
end
+ create_table :cars, :force => true do |t|
+ t.string :name
+ t.integer :engines_count
+ t.integer :wheels_count
+ end
+
create_table :categories, :force => true do |t|
t.string :name, :null => false
t.string :type
@@ -179,6 +185,9 @@ ActiveRecord::Schema.define do
end
add_index :edges, [:source_id, :sink_id], :unique => true, :name => 'unique_edge_index'
+ create_table :engines, :force => true do |t|
+ t.integer :car_id
+ end
create_table :entrants, :force => true do |t|
t.string :name, :null => false
@@ -566,6 +575,10 @@ ActiveRecord::Schema.define do
t.integer :zine_id
end
+ create_table :wheels, :force => true do |t|
+ t.references :wheelable, :polymorphic => true
+ end
+
create_table :zines, :force => true do |t|
t.string :title
end
diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb
index def8eca89f..f537818300 100644
--- a/activesupport/lib/active_support/all.rb
+++ b/activesupport/lib/active_support/all.rb
@@ -1,4 +1,3 @@
require 'active_support'
require 'active_support/time'
require 'active_support/core_ext'
-require 'active_support/json'
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index c4e1eb2c04..1c7802f7de 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -432,7 +432,7 @@ module ActiveSupport
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
- ([self] + self.descendants).each do |target|
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target|
chain = target.send("_#{name}_callbacks")
yield chain, type, filters, options
target.__define_runner(name)
@@ -506,7 +506,7 @@ module ActiveSupport
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
- self.descendants.each do |target|
+ ActiveSupport::DescendantsTracker.descendants(self).each do |target|
chain = target.send("_#{symbol}_callbacks")
callbacks.each { |c| chain.delete(c) }
target.__define_runner(symbol)
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 7d58a8b56a..3e5d1a2a42 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -2,54 +2,49 @@ require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
class Class #:nodoc:
- # Returns an array with the names of the subclasses of +self+ as strings.
- #
- # Integer.subclasses # => ["Bignum", "Fixnum"]
- def subclasses
- Class.subclasses_of(self).map { |o| o.to_s }
- end
-
# Rubinius
if defined?(Class.__subclasses__)
+ alias :subclasses :__subclasses__
+
def descendants
- subclasses = []
- __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendants }
- subclasses
+ descendants = []
+ __subclasses__.each do |k|
+ descendants << k
+ descendants.concat k.descendants
+ end
+ descendants
end
- else
- # MRI
+ else # MRI
begin
ObjectSpace.each_object(Class.new) {}
def descendants
- subclasses = []
+ descendants = []
ObjectSpace.each_object(class << self; self; end) do |k|
- subclasses << k unless k == self
+ descendants.unshift k unless k == self
end
- subclasses
+ descendants
end
- # JRuby
- rescue StandardError
+ rescue StandardError # JRuby
def descendants
- subclasses = []
+ descendants = []
ObjectSpace.each_object(Class) do |k|
- subclasses << k if k < self
+ descendants.unshift k if k < self
end
- subclasses.uniq!
- subclasses
+ descendants.uniq!
+ descendants
end
end
- end
- # Exclude this class unless it's a subclass of our supers and is defined.
- # We check defined? in case we find a removed class that has yet to be
- # garbage collected. This also fails for anonymous classes -- please
- # submit a patch if you have a workaround.
- def self.subclasses_of(*superclasses) #:nodoc:
- subclasses = []
- superclasses.each do |klass|
- subclasses.concat klass.descendants.select {|k| k.anonymous? || k.reachable?}
+ # Returns an array with the direct children of +self+.
+ #
+ # Integer.subclasses # => [Bignum, Fixnum]
+ def subclasses
+ subclasses, chain = [], descendants
+ chain.each do |k|
+ subclasses << k unless chain.any? { |c| c > k }
+ end
+ subclasses
end
- subclasses
end
end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 28fec5394f..e6a213625c 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -1,7 +1,8 @@
require 'date'
require 'active_support/duration'
-require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/date/zones'
+require 'active_support/core_ext/time/zones'
class Date
if RUBY_VERSION < '1.9'
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index ba17b0a06b..092f936961 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,6 +1,6 @@
require 'date'
require 'active_support/inflector'
-require 'active_support/core_ext/time/calculations'
+require 'active_support/core_ext/date/zones'
class Date
DATE_FORMATS = {
@@ -13,10 +13,10 @@ class Date
}
# Ruby 1.9 has Date#to_time which converts to localtime only.
- remove_method :to_time if instance_methods.include?(:to_time)
+ remove_method :to_time if method_defined?(:to_time)
# Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
- remove_method :xmlschema if instance_methods.include?(:xmlschema)
+ remove_method :xmlschema if method_defined?(:xmlschema)
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
@@ -81,16 +81,6 @@ class Date
def to_time(form = :local)
::Time.send("#{form}_time", year, month, day)
end
-
- # Converts Date to a TimeWithZone in the current zone if Time.zone_default is set,
- # otherwise converts Date to a Time via Date#to_time
- def to_time_in_current_zone
- if ::Time.zone_default
- ::Time.zone.local(year, month, day)
- else
- to_time
- end
- end
# Converts a Date instance to a DateTime, where the time is set to the beginning of the day
# and UTC offset is set to 0.
diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb
new file mode 100644
index 0000000000..3a83af6be2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date/zones.rb
@@ -0,0 +1,14 @@
+require 'date'
+require 'active_support/core_ext/time/zones'
+
+class Date
+ # Converts Date to a TimeWithZone in the current zone if Time.zone_default is set,
+ # otherwise converts Date to a Time via Date#to_time
+ def to_time_in_current_zone
+ if ::Time.zone_default
+ ::Time.zone.local(year, month, day)
+ else
+ to_time
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 26979aa906..1dc3933e12 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -1,5 +1,6 @@
require 'rational' unless RUBY_VERSION >= '1.9.2'
require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/time/zones'
class DateTime
class << self
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index c3f0acce25..47b8aa59fb 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -1,6 +1,7 @@
require 'active_support/inflector'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/date_time/calculations'
+require 'active_support/values/time_zone'
class DateTime
# Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows
diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb
index 6002d4ad2a..a7411d54ae 100644
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/time/zones'
+
class DateTime
# Returns the simultaneous time in <tt>Time.zone</tt>.
#
diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb
index d023b4bcff..a1c351bfd9 100644
--- a/activesupport/lib/active_support/core_ext/logger.rb
+++ b/activesupport/lib/active_support/core_ext/logger.rb
@@ -18,7 +18,7 @@ end
require 'logger'
-# Extensions to the built in Ruby logger.
+# Extensions to the built-in Ruby logger.
#
# If you want to use the default log formatter as defined in the Ruby core, then you
# will need to set the formatter for the logger as in:
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 3a6100f776..27618b55c6 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -6,9 +6,9 @@ require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
require 'active_support/core_ext/object/misc'
-require 'active_support/core_ext/object/extending'
require 'active_support/core_ext/object/returning'
+require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/object/with_options'
diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb
deleted file mode 100644
index c4c37b6a2a..0000000000
--- a/activesupport/lib/active_support/core_ext/object/extending.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require 'active_support/core_ext/class/subclasses'
-
-class Object
- # Exclude this class unless it's a subclass of our supers and is defined.
- # We check defined? in case we find a removed class that has yet to be
- # garbage collected. This also fails for anonymous classes -- please
- # submit a patch if you have a workaround.
- def subclasses_of(*superclasses) #:nodoc:
- Class.subclasses_of(*superclasses)
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb
new file mode 100644
index 0000000000..14ef27340e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/to_json.rb
@@ -0,0 +1,19 @@
+# Hack to load json gem first so we can overwrite its to_json.
+begin
+ require 'json'
+rescue LoadError
+end
+
+# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
+# their default behavior. That said, we need to define the basic to_json method in all of them,
+# otherwise they will always use to_json gem implementation, which is backwards incompatible in
+# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
+# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
+[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
+ klass.class_eval <<-RUBY, __FILE__, __LINE__
+ # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
+ def to_json(options = nil)
+ ActiveSupport::JSON.encode(self, options)
+ end
+ RUBY
+end
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 3dfe996d06..16ccd36458 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -2,7 +2,7 @@
require 'active_support/multibyte'
class String
- if '1.9'.respond_to?(:force_encoding)
+ if RUBY_VERSION >= "1.9"
# == Multibyte proxy
#
# +mb_chars+ is a multibyte safe proxy for string methods.
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 3c218d88e5..90b6cd3685 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -40,6 +40,11 @@ class Time
def local_time(*args)
time_with_datetime_fallback(:local, *args)
end
+
+ # Returns <tt>Time.zone.now</tt> when <tt>config.time_zone</tt> is set, otherwise just returns <tt>Time.now</tt>.
+ def current
+ ::Time.zone_default ? ::Time.zone.now : ::Time.now
+ end
end
# Tells whether the Time object's time lies in the past
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index a02402aa3f..6b9ee84d5c 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -41,11 +41,6 @@ class Time
::Time.zone = old_zone
end
- # Returns <tt>Time.zone.now</tt> when <tt>config.time_zone</tt> is set, otherwise just returns <tt>Time.now</tt>.
- def current
- ::Time.zone_default ? ::Time.zone.now : ::Time.now
- end
-
private
def get_zone(time_zone)
return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone)
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index feb1508586..f54f65dcf0 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,4 +1,5 @@
require "active_support/notifications"
+require "active_support/core_ext/array/wrap"
module ActiveSupport
module Deprecation
@@ -7,11 +8,11 @@ module ActiveSupport
attr_accessor :debug
def behavior
- @behavior ||= DEFAULT_BEHAVIORS[:stderr]
+ @behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
end
def behavior=(behavior)
- @behavior = DEFAULT_BEHAVIORS[behavior] || behavior
+ @behavior = Array.wrap(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
end
end
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index 03c445ffbf..49d58cd3a1 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -4,8 +4,9 @@ module ActiveSupport
attr_accessor :silenced
def warn(message = nil, callstack = caller)
- if behavior && !silenced
- behavior.call(deprecation_message(callstack, message), callstack)
+ return if silenced
+ deprecation_message(callstack, message).tap do |m|
+ behavior.each { |b| b.call(m, callstack) }
end
end
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
index a587d7770c..6cba84d79e 100644
--- a/activesupport/lib/active_support/descendants_tracker.rb
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -4,16 +4,23 @@ module ActiveSupport
# This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace.
module DescendantsTracker
- @@descendants = Hash.new { |h, k| h[k] = [] }
+ @@direct_descendants = Hash.new { |h, k| h[k] = [] }
- def self.descendants
- @@descendants
+ def self.direct_descendants(klass)
+ @@direct_descendants[klass]
+ end
+
+ def self.descendants(klass)
+ @@direct_descendants[klass].inject([]) do |descendants, klass|
+ descendants << klass
+ descendants.concat klass.descendants
+ end
end
def self.clear
- @@descendants.each do |klass, descendants|
+ @@direct_descendants.each do |klass, descendants|
if ActiveSupport::Dependencies.autoloaded?(klass)
- @@descendants.delete(klass)
+ @@direct_descendants.delete(klass)
else
descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
end
@@ -26,14 +33,11 @@ module ActiveSupport
end
def direct_descendants
- @@descendants[self]
+ DescendantsTracker.direct_descendants(self)
end
def descendants
- @@descendants[self].inject([]) do |descendants, klass|
- descendants << klass
- descendants.concat klass.descendants
- end
+ DescendantsTracker.descendants(self)
end
end
end \ No newline at end of file
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 5f5b264eb9..cd658fe173 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -1,16 +1,15 @@
module ActiveSupport
# This class is responsible to track files and invoke the given block
# whenever one of these files are changed. For example, this class
- # is used by Rails to reload routes whenever they are changed upon
- # a new request.
+ # is used by Rails to reload the I18n framework whenever they are
+ # changed upon a new request.
#
- # routes_reloader = ActiveSupport::FileUpdateChecker.new(paths) do
- # paths.each { |p| load(p) }
- # Rails::Application.routes.reload!
+ # i18n_reloader = ActiveSupport::FileUpdateChecker.new(paths) do
+ # I18n.reload!
# end
#
# ActionDispatch::Callbacks.to_prepare do
- # routes_reloader.execute_if_updated
+ # i18n_reloader.execute_if_updated
# end
#
class FileUpdateChecker
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index d82e54f1d4..db09919fd3 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -1,6 +1,7 @@
require "active_support"
require "rails"
require "active_support/file_update_checker"
+require "active_support/core_ext/array/wrap"
module I18n
class Railtie < Rails::Railtie
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index dd94315111..2f9588e0f4 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,20 +1,18 @@
-# encoding: utf-8
+require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/deprecation'
+require 'active_support/json/variable'
+
require 'bigdecimal'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
+require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/instance_variables'
-require 'active_support/deprecation'
-
-require 'active_support/time'
-
-# Hack to load json gem first so we can overwrite its to_json.
-begin
- require 'json'
-rescue LoadError
-end
+require 'time'
+require 'active_support/core_ext/time/conversions'
+require 'active_support/core_ext/date_time/conversions'
+require 'active_support/core_ext/date/conversions'
module ActiveSupport
class << self
@@ -128,20 +126,6 @@ module ActiveSupport
end
end
-# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
-# their default behavior. That said, we need to define the basic to_json method in all of them,
-# otherwise they will always use to_json gem implementation, which is backwards incompatible in
-# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
-# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
-[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
- klass.class_eval <<-RUBY, __FILE__, __LINE__
- # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
- def to_json(options = nil)
- ActiveSupport::JSON.encode(self, options)
- end
- RUBY
-end
-
class Object
def as_json(options = nil) #:nodoc:
if respond_to?(:to_hash)
@@ -152,12 +136,6 @@ class Object
end
end
-# A string that returns itself as its JSON-encoded form.
-class ActiveSupport::JSON::Variable < String
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) self end #:nodoc:
-end
-
class TrueClass
AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze
def as_json(options = nil) AS_JSON end #:nodoc:
diff --git a/activesupport/lib/active_support/json/variable.rb b/activesupport/lib/active_support/json/variable.rb
index daa7449b71..5685ed18b7 100644
--- a/activesupport/lib/active_support/json/variable.rb
+++ b/activesupport/lib/active_support/json/variable.rb
@@ -2,10 +2,8 @@ module ActiveSupport
module JSON
# A string that returns itself as its JSON-encoded form.
class Variable < String
- private
- def rails_to_json(*)
- self
- end
+ def as_json(options = nil) self end #:nodoc:
+ def encode_json(encoder) self end #:nodoc:
end
end
end
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 8823e4a5ed..51c2a0edac 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -325,18 +325,6 @@ module ActiveSupport #:nodoc:
end
alias_method :[], :slice
- # Like <tt>String#slice!</tt>, except instead of byte offsets you specify character offsets.
- #
- # Example:
- # s = 'こんにちは'
- # s.mb_chars.slice!(2..3).to_s #=> "にち"
- # s #=> "こんは"
- def slice!(*args)
- slice = self[*args]
- self[*args] = ''
- slice
- end
-
# Limit the byte size of the string to a number of bytes without breaking characters. Usable
# when the storage for a string is limited for some reason.
#
@@ -425,14 +413,14 @@ module ActiveSupport #:nodoc:
chars(Unicode.tidy_bytes(@wrapped_string, force))
end
- %w(lstrip rstrip strip reverse upcase downcase tidy_bytes capitalize).each do |method|
- define_method("#{method}!") do |*args|
- unless args.nil?
- @wrapped_string = send(method, *args).to_s
- else
- @wrapped_string = send(method).to_s
+ %w(capitalize downcase lstrip reverse rstrip slice strip tidy_bytes upcase).each do |method|
+ # Only define a corresponding bang method for methods defined in the proxy; On 1.9 the proxy will
+ # exclude lstrip!, rstrip! and strip! because they are already work as expected on multibyte strings.
+ if public_method_defined?(method)
+ define_method("#{method}!") do |*args|
+ @wrapped_string = send(args.nil? ? method : method, *args).to_s
+ self
end
- self
end
end
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index 784c7173a9..86f057d676 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -24,6 +24,7 @@ require 'active_support/core_ext/date/acts_like'
require 'active_support/core_ext/date/freeze'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
+require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/calculations'
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 05b40298d3..49dd8a1b99 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,11 +1,5 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
-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
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following:
#
@@ -201,6 +195,12 @@ 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
@name = name
@utc_offset = utc_offset
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb
index b7f3dd9930..08bb13dd35 100644
--- a/activesupport/test/core_ext/class_test.rb
+++ b/activesupport/test/core_ext/class_test.rb
@@ -1,29 +1,27 @@
require 'abstract_unit'
require 'active_support/core_ext/class'
-class A
-end
+class ClassTest < Test::Unit::TestCase
+ class Parent; end
+ class Foo < Parent; end
+ class Bar < Foo; end
+ class Baz < Bar; end
-module X
- class B
- end
-end
+ class A < Parent; end
+ class B < A; end
+ class C < B; end
-module Y
- module Z
- class C
- end
+ def test_descendants
+ assert_equal [Foo, Bar, Baz, A, B, C], Parent.descendants
+ assert_equal [Bar, Baz], Foo.descendants
+ assert_equal [Baz], Bar.descendants
+ assert_equal [], Baz.descendants
end
-end
-class ClassTest < Test::Unit::TestCase
- def test_retrieving_subclasses
- @parent = eval("class D; end; D")
- @sub = eval("class E < D; end; E")
- @subofsub = eval("class F < E; end; F")
- assert_equal 2, @parent.subclasses.size
- assert_equal [@subofsub.to_s], @sub.subclasses
- assert_equal [], @subofsub.subclasses
- assert_equal [@sub.to_s, @subofsub.to_s].sort, @parent.subclasses.sort
+ def test_subclasses
+ assert_equal [Foo, A], Parent.subclasses
+ assert_equal [Bar], Foo.subclasses
+ assert_equal [Baz], Bar.subclasses
+ assert_equal [], Baz.subclasses
end
-end
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb
index 5e03389dc2..6588f2e345 100644
--- a/activesupport/test/core_ext/object_and_class_ext_test.rb
+++ b/activesupport/test/core_ext/object_and_class_ext_test.rb
@@ -40,55 +40,6 @@ class Foo
include Bar
end
-class ClassExtTest < Test::Unit::TestCase
- def test_subclasses_of_should_find_nested_classes
- assert Class.subclasses_of(ClassK).include?(Nested::ClassL)
- end
-
- def test_subclasses_of_should_not_return_removed_classes
- # First create the removed class
- old_class = Nested.class_eval { remove_const :ClassL }
- new_class = Class.new(ClassK)
- Nested.const_set :ClassL, new_class
- assert_equal "Nested::ClassL", new_class.name # Sanity check
-
- subclasses = Class.subclasses_of(ClassK)
- assert subclasses.include?(new_class)
- assert ! subclasses.include?(old_class)
- ensure
- Nested.const_set :ClassL, old_class unless defined?(Nested::ClassL)
- end
-
- def test_subclasses_of_should_not_trigger_const_missing
- const_missing = false
- Nested.on_const_missing { const_missing = true }
-
- subclasses = Class.subclasses_of ClassK
- assert !const_missing
- assert_equal [ Nested::ClassL ], subclasses
-
- removed = Nested.class_eval { remove_const :ClassL } # keep it in memory
- subclasses = Class.subclasses_of ClassK
- assert !const_missing
- assert subclasses.empty?
- ensure
- Nested.const_set :ClassL, removed unless defined?(Nested::ClassL)
- end
-
- def test_subclasses_of_with_multiple_roots
- classes = Class.subclasses_of(ClassI, ClassK)
- assert_equal %w(ClassJ Nested::ClassL), classes.collect(&:to_s).sort
- end
-
- def test_subclasses_of_doesnt_find_anonymous_classes
- assert_equal [], Class.subclasses_of(Foo)
- bar = Class.new(Foo)
- assert_nothing_raised do
- assert_equal [bar], Class.subclasses_of(Foo)
- end
- end
-end
-
class ObjectTests < ActiveSupport::TestCase
class DuckTime
def acts_like_time?
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index cf27357b32..cad0810241 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -80,6 +80,19 @@ class DeprecationTest < ActiveSupport::TestCase
assert_deprecated(/foo=nil/) { @dtc.partially }
end
+ def test_several_behaviors
+ @a, @b = nil, nil
+
+ ActiveSupport::Deprecation.behavior = [
+ Proc.new { |msg, callstack| @a = msg },
+ Proc.new { |msg, callstack| @b = msg }
+ ]
+
+ @dtc.partially
+ assert_match(/foo=nil/, @a)
+ assert_match(/foo=nil/, @b)
+ end
+
def test_deprecated_instance_variable_proxy
assert_not_deprecated { @dtc.request.size }
@@ -123,6 +136,13 @@ class DeprecationTest < ActiveSupport::TestCase
assert_equal 123, result
end
+ def test_assert_deprecated_warn_work_with_default_behavior
+ ActiveSupport::Deprecation.instance_variable_set('@behavior' , nil)
+ assert_deprecated('abc') do
+ ActiveSupport::Deprecation.warn 'abc'
+ end
+ end
+
def test_silence
ActiveSupport::Deprecation.silence do
assert_not_deprecated { @dtc.partially }
diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test.rb
index ff24e310de..79fb893592 100644
--- a/activesupport/test/descendants_tracker_test.rb
+++ b/activesupport/test/descendants_tracker_test.rb
@@ -37,7 +37,9 @@ class DescendantsTrackerTest < Test::Unit::TestCase
def test_clear_with_autoloaded_parent_children_and_granchildren
mark_as_autoloaded(*ALL) do
ActiveSupport::DescendantsTracker.clear
- assert ActiveSupport::DescendantsTracker.descendants.slice(*ALL).empty?
+ ALL.each do |k|
+ assert ActiveSupport::DescendantsTracker.descendants(k).empty?
+ end
end
end
@@ -64,12 +66,12 @@ class DescendantsTrackerTest < Test::Unit::TestCase
old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
- old_descendants = ActiveSupport::DescendantsTracker.descendants.dup
+ old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup
old_descendants.each { |k, v| old_descendants[k] = v.dup }
yield
ensure
ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded
- ActiveSupport::DescendantsTracker.descendants.replace(old_descendants)
+ ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants)
end
end \ No newline at end of file
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index a8ecf4e4cf..a679efb41e 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -1,7 +1,5 @@
# encoding: utf-8
require 'abstract_unit'
-require 'bigdecimal'
-require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/json'
class TestJSONEncoding < Test::Unit::TestCase
@@ -138,6 +136,10 @@ class TestJSONEncoding < Test::Unit::TestCase
ActiveSupport.use_standard_json_time_format = false
end
+ def test_hash_with_time_to_json
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
+ end
+
def test_nested_hash_with_float
assert_nothing_raised do
hash = {
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 610295fa89..78232d8eb5 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -123,22 +123,30 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert_equal 'こに わ', @chars
end
- def test_overridden_bang_methods_return_self
- [:rstrip!, :lstrip!, :strip!, :reverse!, :upcase!, :downcase!, :capitalize!].each do |method|
- assert_equal @chars.object_id, @chars.send(method).object_id
- end
+ %w{capitalize downcase lstrip reverse rstrip strip upcase}.each do |method|
+ class_eval(<<-EOTESTS)
+ def test_#{method}_bang_should_return_self
+ assert_equal @chars.object_id, @chars.send("#{method}!").object_id
+ end
+
+ def test_#{method}_bang_should_change_wrapped_string
+ original = ' él piDió Un bUen café '
+ proxy = chars(original.dup)
+ proxy.send("#{method}!")
+ assert_not_equal original, proxy.to_s
+ end
+ EOTESTS
end
- def test_overridden_bang_methods_change_wrapped_string
- [:rstrip!, :lstrip!, :strip!, :reverse!, :upcase!, :downcase!].each do |method|
- original = ' Café '
- proxy = chars(original.dup)
- proxy.send(method)
- assert_not_equal original, proxy.to_s
- end
- proxy = chars('òu')
- proxy.capitalize!
- assert_equal 'Òu', proxy.to_s
+ def test_tidy_bytes_bang_should_return_self
+ assert_equal @chars.object_id, @chars.tidy_bytes!.object_id
+ end
+
+ def test_tidy_bytes_bang_should_change_wrapped_string
+ original = " Un bUen café \x92"
+ proxy = chars(original.dup)
+ proxy.tidy_bytes!
+ assert_not_equal original, proxy.to_s
end
if RUBY_VERSION >= '1.9'
@@ -417,8 +425,9 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
end
def test_slice_bang_removes_the_slice_from_the_receiver
- @chars.slice!(1..2)
- assert_equal 'こわ', @chars
+ chars = 'úüù'.mb_chars
+ chars.slice!(0,2)
+ assert_equal 'úü', chars
end
def test_slice_should_throw_exceptions_on_invalid_arguments
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 3d1bae163f..d340bed444 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/json'
+require 'active_support/core_ext/object/to_json'
class OrderedHashTest < Test::Unit::TestCase
def setup
diff --git a/rails.gemspec b/rails.gemspec
index 236203c626..2d10ce78a0 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -25,5 +25,5 @@ Gem::Specification.new do |s|
s.add_dependency('activeresource', version)
s.add_dependency('actionmailer', version)
s.add_dependency('railties', version)
- s.add_dependency('bundler', '>= 0.9.26')
+ s.add_dependency('bundler', '>= 1.0.0.beta.3')
end
diff --git a/railties/README b/railties/README
index b8c84dd07d..d8be15e346 100644
--- a/railties/README
+++ b/railties/README
@@ -29,13 +29,13 @@ link:files/vendor/rails/actionpack/README.html.
== Getting Started
1. At the command prompt, create a new Rails application:
- <tt>rails myapp</tt> (where <tt>myapp</tt> is the application name)
+ <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
2. Change directory to <tt>myapp</tt> and start the web server:
<tt>cd myapp; rails server</tt> (run with --help for options)
3. Go to http://localhost:3000/ and you'll see:
- "Welcome aboard: You're riding the Rails!"
+ "Welcome aboard: You're riding Ruby on Rails!"
4. Follow the guidelines to start developing your application. You can find
the following resources handy:
diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile
index b32ca4fce9..7dcaf508c6 100644
--- a/railties/guides/source/3_0_release_notes.textile
+++ b/railties/guides/source/3_0_release_notes.textile
@@ -339,7 +339,9 @@ h5. Other Changes
* You no longer need to place a minus sign at the end of a ruby interpolation inside an ERb template to remove the trailing carriage return in the HTML output.
* Added +grouped_collection_select+ helper to Action View.
* +content_for?+ has been added allowing you to check for the existence of content in a view before rendering.
-
+* passing +:value => nil+ to form helpers will set the field's +value+ attribute to nil as opposed to using the default value
+* passing +:id => nil+ to form helpers will cause those fields to be rendered with no +id+ attribute
+* passing +:alt => nil+ to +image_tag+ will cause the +img+ tag to render with no +alt+ attribute
h3. Active Model
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index cfd4ae55cc..84c33e34f9 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -86,9 +86,9 @@ The following methods skip validations, and will save the object to the database
* +update_attribute+
* +update_counters+
-Note that +save+ also has the ability to skip validations if passed +false+ as argument. This technique should be used with caution.
+Note that +save+ also has the ability to skip validations if passed +:validate => false+ as argument. This technique should be used with caution.
-* +save(false)+
+* +save(:validate => false)+
h4. +valid?+ and +invalid?+
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 844b9428bd..58824d7aeb 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -387,40 +387,6 @@ TIP: Since +with_options+ forwards calls to its receiver they can be nested. Eac
NOTE: Defined in +active_support/core_ext/object/with_options.rb+.
-h5. +subclasses_of+
-
-The method +subclasses_of+ receives an arbitrary number of class objects and returns all their anonymous or reachable descendants as a single array:
-
-<ruby>
-class C; end
-subclasses_of(C) # => []
-
-subclasses_of(Integer) # => [Bignum, Fixnum]
-
-module M
- class A; end
- class B1 < A; end
- class B2 < A; end
-end
-
-module N
- class C < M::B1; end
-end
-
-subclasses_of(M::A) # => [N::C, M::B2, M::B1]
-</ruby>
-
-The order in which these classes are returned is unspecified. The returned collection may have duplicates:
-
-<ruby>
-subclasses_of(Numeric, Integer)
-# => [Bignum, Float, Fixnum, Integer, Date::Infinity, Rational, BigDecimal, Bignum, Fixnum]
-</ruby>
-
-See also +Class#subclasses+ in "Extensions to +Class+ FIX THIS LINK":FIXME.
-
-NOTE: Defined in +active_support/core_ext/object/extending.rb+.
-
h4. Instance Variables
Active Support provides several methods to ease access to instance variables.
@@ -1141,36 +1107,51 @@ If for whatever reason an application loads the definition of a mailer class and
NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+.
-h4. Descendants
+h4. Subclasses & Descendants
h5. +subclasses+
-The +subclasses+ method returns the names of all the anonymous or reachable descendants of its receiver as an array of strings:
+The +subclasses+ method returns the subclasses of the receiver:
<ruby>
class C; end
C.subclasses # => []
-Integer.subclasses # => ["Bignum", "Fixnum"]
+class B < C; end
+C.subclasses # => [B]
-module M
- class A; end
- class B1 < A; end
- class B2 < A; end
-end
+class A < B; end
+C.subclasses # => [B]
-module N
- class C < M::B1; end
-end
-
-M::A.subclasses # => ["N::C", "M::B2", "M::B1"]
+class D < C; end
+C.subclasses # => [B, D]
</ruby>
-The order in which these class names are returned is unspecified.
+The order in which these classes are returned is unspecified.
+
+WARNING: This method is redefined in some Rails core classes but should be all compatible in Rails 3.1.
+
+NOTE: Defined in +active_support/core_ext/class/subclasses.rb+.
-See also +Object#subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME.
+h5. +descendants+
+
+The +descendants+ method returns all classes that are <tt>&lt;</tt> than its receiver:
+
+<ruby>
+class C; end
+C.descendants # => []
+
+class B < C; end
+C.descendants # => [B]
+
+class A < B; end
+C.descendants # => [B, A]
+
+class D < C; end
+C.descendants # => [B, A, D]
+</ruby>
-WARNING: This method is redefined in some Rails core classes.
+The order in which these classes are returned is unspecified.
NOTE: Defined in +active_support/core_ext/class/subclasses.rb+.
diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile
index 335d17579d..c69f2ae8c9 100644
--- a/railties/guides/source/association_basics.textile
+++ b/railties/guides/source/association_basics.textile
@@ -137,6 +137,16 @@ end
!images/has_many_through.png(has_many :through Association Diagram)!
+The collection of join models can be managed via the API. For example, if you assign
+
+<ruby>
+physician.patients = patients
+</ruby>
+
+new join models are created for newly associated objects, and if some are gone their rows are deleted.
+
+WARNING: Automatic deletion of join models is direct, no destroy callbacks are triggered.
+
The +has_many :through+ association is also useful for setting up "shortcuts" through nested +has_many+ associations. For example, if a document has many sections, and a section has many paragraphs, you may sometimes want to get a simple collection of all paragraphs in the document. You could set that up this way:
<ruby>
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 6abb3ed9f4..f547f29087 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -958,7 +958,7 @@ You'll see a bit more complexity here than you did in the controller for posts.
In addition, the code takes advantage of some of the methods available for an association. We use the +create+ method on +@post.comments+ to create and save the comment. This will automatically link the comment so that it belongs to that particular post.
-Once we have made the new comment, we send the user back to the original post using the +post_path(@post)+ helper. As we have already seen, this calls the +show+ action of the +PostsController+ which in turn renders the +show.html.erb+ template. This is where we want the comment to show, so let's add that to the +app/view/posts/show.html.erb+.
+Once we have made the new comment, we send the user back to the original post using the +post_path(@post)+ helper. As we have already seen, this calls the +show+ action of the +PostsController+ which in turn renders the +show.html.erb+ template. This is where we want the comment to show, so let's add that to the +app/views/posts/show.html.erb+.
<erb>
<p class="notice"><%= notice %></p>
diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb
index be077fcd2f..a930db0f1d 100644
--- a/railties/guides/source/index.html.erb
+++ b/railties/guides/source/index.html.erb
@@ -68,7 +68,7 @@ Ruby on Rails Guides
<% end %>
<%= guide("Action View Form Helpers", 'form_helpers.html', :ticket => 1) do %>
- <p>Guide to using built in Form helpers.</p>
+ <p>Guide to using built-in Form helpers.</p>
<% end %>
</dl>
diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile
index e458413b35..7a44ef7c77 100644
--- a/railties/guides/source/initialization.textile
+++ b/railties/guides/source/initialization.textile
@@ -11,7 +11,7 @@ This guide first describes the process of +rails server+ then explains the Passe
h3. Launch!
-As of Rails 3, +script/server+ has become +rails server+. This was done to centralise all rails related commands to one common file.
+As of Rails 3, +script/server+ has become +rails server+. This was done to centralize all rails related commands to one common file.
The actual +rails+ command is kept in _railties/bin/rails_ and goes like this:
@@ -58,11 +58,8 @@ In +script/rails+ we see the following:
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
- ENV_PATH = File.expand_path('../../config/environment', __FILE__)
- BOOT_PATH = File.expand_path('../../config/boot', __FILE__)
- APP_PATH = File.expand_path('../../config/application', __FILE__)
-
- require BOOT_PATH
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
+ require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'
</ruby>
@@ -79,15 +76,19 @@ h3. _config/boot.rb_
_config/boot.rb_ is the first stop for everything for initializing your application. This boot process does quite a bit of work for you and so this section attempts to go in-depth enough to explain what each of the pieces does.
<ruby>
- # Use Bundler (preferred)
+ require 'rubygems'
+
+ # Set up gems listed in the Gemfile.
+ gemfile = File.expand_path('../../Gemfile', __FILE__)
begin
- require File.expand_path('../../.bundle/environment', __FILE__)
- rescue LoadError
- require 'rubygems'
+ ENV['BUNDLE_GEMFILE'] = gemfile
require 'bundler'
Bundler.setup
- end
-
+ rescue Bundler::GemNotFound => e
+ STDERR.puts e.message
+ STDERR.puts "Try running `bundle install`."
+ exit!
+ end if File.exist?(gemfile)
</ruby>
h3. Bundled Rails (3.x)
@@ -140,8 +141,8 @@ Here the only two gems we need are +rails+ and +sqlite3-ruby+, so it seems. This
* activesupport-3.0.0.beta4.gem
* arel-0.4.0.gem
* builder-2.1.2.gem
-* bundler-0.9.26.gem
-* erubis-2.6.5.gem
+* bundler-1.0.0.beta.2.gem
+* erubis-2.6.6.gem
* i18n-0.4.1.gem
* mail-2.2.4.gem
* memcache-client-1.8.3.gem
@@ -156,7 +157,7 @@ Here the only two gems we need are +rails+ and +sqlite3-ruby+, so it seems. This
* sqlite3-ruby-1.3.0.gem
* text-format-1.0.0.gem
* text-hyphen-1.0.0.gem
-* thor-0.13.6.gem
+* thor-0.13.7.gem
* treetop-1.4.8.gem
* tzinfo-0.3.22.gem
@@ -164,33 +165,7 @@ TODO: Prettify when it becomes more stable.
I won't go into what each of these gems are, as that is really something that needs covering on a case-by-case basis. We will however just dig a little under the surface of Bundler.
-Back in _config/boot.rb_, the first line will try to include _.bundle/environment.rb_, which doesn't exist in a bare-bones Rails application and because this file does not exist Ruby will raise a +LoadError+ which will be rescued and run the following code:
-
-<ruby>
- require 'rubygems'
- require 'bundler'
- Bundler.setup
-</ruby>
-
-+Bundler.setup+ here will load and parse the +Gemfile+ and add the _lib_ directory of the gems mentioned **and** their dependencies (**and** their dependencies' dependencies, and so on) to the +$LOAD_PATH+.
-
-Now we will go down the alternate timeline where we generate a _.bundle/environment.rb_ file using the +bundle lock+ command. This command also creates a _Gemfile.lock_ file which is actually a YAML file loaded by this method in Bundler before it moves on to check for _Gemfile_:
-
-<ruby>
- def definition(gemfile = default_gemfile)
- configure
- root = Pathname.new(gemfile).dirname
- lockfile = root.join("Gemfile.lock")
- if lockfile.exist?
- Definition.from_lock(lockfile)
- else
- Definition.from_gemfile(gemfile)
- end
- end
-</ruby>
-
-
-The _.bundle/environment.rb_ file adds the _lib_ directory of all the gems specified in +Gemfile.lock+ to +$LOAD_PATH+.
+Back in _config/boot.rb_, we call +Bundler.setup+ which will load and parse the +Gemfile+ and add the _lib_ directory of the gems mentioned **and** their dependencies (**and** their dependencies' dependencies, and so on) to the +$LOAD_PATH+.
h3. Requiring Rails
@@ -326,6 +301,11 @@ As you can see for the duration of the +eager_autoload+ block the class variable
module ActiveSupport
extend ActiveSupport::Autoload
+ autoload :DescendantsTracker
+ autoload :FileUpdateChecker
+ autoload :LogSubscriber
+ autoload :Notifications
+
# TODO: Narrow this list down
eager_autoload do
autoload :BacktraceCleaner
@@ -348,7 +328,6 @@ As you can see for the duration of the +eager_autoload+ block the class variable
autoload :OptionMerger
autoload :OrderedHash
autoload :OrderedOptions
- autoload :Notifications
autoload :Rescuable
autoload :SecureRandom
autoload :StringInquirer
@@ -589,19 +568,20 @@ This file (_railties/lib/rails.rb_) requires the very, very basics that Rails ne
require 'action_dispatch/railtie'
</ruby>
-+require 'pathname'+ requires the Pathname class which is used for returning a Pathname object for +Rails.root+ so that instead of doing:
++require 'pathname'+ requires the Pathname class which is used for returning a Pathname object for +Rails.root+. Although is coming to use this path name to generate paths as below:
<ruby>
- File.join(Rails.root, "app/controllers")
+ Rails.root.join("app/controllers")
</ruby>
-You may do:
+Pathname can also be converted to string, so the following syntax is preferred:
<ruby>
- Rails.root.join("app/controllers")
+ "#{Rails.root}/app/controllers"
</ruby>
-Although this is not new to Rails 3 (it was available in 2.3.5), it is something worthwhile pointing out.
+
+This works because Ruby automatically handles file path conversions. Although this is not new to Rails 3 (it was available in 2.3.5), it is something worthwhile pointing out.
Inside this file there are other helpful helper methods defined, such as +Rails.root+, +Rails.env+, +Rails.logger+ and +Rails.application+.
@@ -1833,7 +1813,7 @@ We do not already have a +Rails.application+, so instead this resorts to calling
end
</ruby>
-This +called_from+ setting looks a little overwhelming to begin with, but the short end of it is that it returns the route to your application's config directory, something like: _/home/you/yourapp/config_. After +called_from+ has been set, +super+ is again called and this means the +Rails::Railtie#inherited+ method (in _railties/lib/rails/railtie.rb_):
+This +called_from+ setting looks a little overwhelming to begin with, but the short end of it is that it returns your application's root, something like: _/home/you/yourapp_. After +called_from+ has been set, +super+ is again called and this means the +Rails::Railtie#inherited+ method (in _railties/lib/rails/railtie.rb_):
<ruby>
def inherited(base)
@@ -3588,28 +3568,24 @@ h3. Appendix A
This file is _activesupport/lib/active_support/inflector/inflections.rb_ and defines the +ActiveSupport::Inflector::Inflections+ class which defines the +singularize+, +pluralize+, +humanize+, +tableize+, +titleize+ and +classify+ methods as well as the code to defining how to work out the irregular, singular, plural and human versions of words. These methods are called +irregular+, +singular+, +plural+ and +human+ respectively, as is the Rails way.
-This file is _activesupport/lib/active_support/inflector/transliterate.rb_ and defines two methods, +transliterate+ and +parameterize+. What +transliterate+ does depends on your Ruby version. If you have something greater than 1.9 installed it will just print out a warning message using the +Kernel#warn+ method (simply called using +warn+) reading "Ruby 1.9 doesn't support Unicode normalization yet". If you're running something that's not 1.9 it will attempt to convert +"föö"+ to +foo+ and if that fails then it'll redefine it.
+This file is _activesupport/lib/active_support/inflector/transliterate.rb_ and defines two methods, +transliterate+ and +parameterize+.
-This file first makes a require to _activesupport/lib/active_support/core_ext/string/multibyte.rb_ which then goes on to require _activesupport/lib/active_support/multibyte.rb_ and that requires _activesupport/core_ext/module/attribute_accessors.rb_. The _attribute_accessors.rb_ file is used to gain access to the +mattr_accessor+ (module attribute accessor) method which is called in _active_suport/multibyte.rb_. Also in _active_support/multibyte.rb_ there's a couple of autoloaded classes:
+This file first requires _activesupport/lib/active_support/core_ext/string/multibyte.rb_, which requires _activesupport/lib/active_support/multibyte.rb_, which subsequently requires _activesupport/core_ext/module/attribute_accessors.rb_. The _attribute_accessors.rb_ file is needed to gain access to the +mattr_accessor+ (module attribute accessor) method, which is called in _active_suport/multibyte.rb_. The file _active_support/multibyte.rb_ also autoloads three other classes:
<ruby>
module ActiveSupport #:nodoc:
module Multibyte
autoload :EncodingError, 'active_support/multibyte/exceptions'
autoload :Chars, 'active_support/multibyte/chars'
- autoload :UnicodeDatabase, 'active_support/multibyte/unicode_database'
- autoload :Codepoint, 'active_support/multibyte/unicode_database'
- autoload :UCD, 'active_support/multibyte/unicode_database'
- ...
+ autoload :Unicode, 'active_support/multibyte/unicode'
+ ...
end
end
</ruby>
-There's also these method definitions:
+There are also these method definitions:
<ruby>
- self.default_normalization_form = :kc
-
# The proxy class returned when calling mb_chars. You can use this accessor to configure your own proxy
# class so you can support other encodings. See the ActiveSupport::Multibyte::Chars implementation for
# an example how to do this.
@@ -3628,63 +3604,92 @@ There's also these method definitions:
These methods are used in _activesupport/lib/active_support/core_ext/string/multibyte.rb_.
-If we go back to _activesupport/lib/active_support/core_ext/string/multibyte.rb_, this file makes a couple of extensions to the +String+ class based on if your version of Ruby's +String+ class responds to the +force_encoding+ method. This method was introduced in Ruby 1.9. If you're using 1.9 the methods are defined like this:
-
-<ruby>
- def mb_chars #:nodoc
- self
- end
-
- def is_utf8? #:nodoc
- case encoding
- when Encoding::UTF_8
- valid_encoding?
- when Encoding::ASCII_8BIT, Encoding::US_ASCII
- dup.force_encoding(Encoding::UTF_8).valid_encoding?
- else
- false
- end
- end
-</ruby>
-
-You can see that calling +mb_chars+ on a +String+ instance in Ruby 1.9 will simply return that +String+ object. +String+ objects in Ruby 1.9 are already multibyte strings, so Rails does not need to do any conversion on them.
-
-The second method, +is_utf8?+ return +true+ if the +String+ object is of the UTF8 encoding or if it's able to be forced into that encoding and +false+ if it can't force its encoding or if the encoding of the string is neither +UTF8+, +ASCII_8BIT+ or +US_ASCII+.
-
-If you're using a Ruby version less than 1.9 there are 3 methods defined instead of 2, and they are defined like this:
+The file _activesupport/lib/active_support/core_ext/string/chars.rb_ defines the default proxy class that will be returned by +mb_chars+.
+
+Because Ruby 1.9's +String+ class has support for multibyte encodings, some methods are defined only for Ruby 1.8:
+
+* +self.wants?+
+* +++
+* +=~+
+* +=~+
+* +center+
+* +include?+
+* +index+
+* +insert+
+* +ljust+
+* +lstrip+, +lstrip!+
+* +ord+
+* +rindex+
+* +rjust+
+* +rstrip+, +rstrip!+
+* +size+
+* +strip+, +strip!+
+
+However, Ruby 1.9 lacks support for some needed operations, so the following methods are defined for both Ruby 1.8 and Ruby 1.9:
+
+* +<=>+
+* +[]=+
+* +capitalize+, +capitalize!+
+* +compose+
+* +decompose+
+* +downcase+, +downcase!+
+* +g_length+
+* +limit+
+* +normalize+
+* +reverse+, +reverse+!
+* +slice+, +slice!+
+* +split+
+* +tidy_bytes+, +tidy_bytes!+
+* +titleize+
+* +upcase+, +upcase!+
+
+<ruby>
+ class String
+ if RUBY_VERSION >= "1.9"
+ def mb_chars
+ if ActiveSupport::Multibyte.proxy_class.consumes?(self)
+ ActiveSupport::Multibyte.proxy_class.new(self)
+ else
+ self
+ end
+ end
-<ruby>
- def mb_chars
- if ActiveSupport::Multibyte.proxy_class.wants?(self)
- ActiveSupport::Multibyte.proxy_class.new(self)
+ def is_utf8? #:nodoc
+ case encoding
+ when Encoding::UTF_8
+ valid_encoding?
+ when Encoding::ASCII_8BIT, Encoding::US_ASCII
+ dup.force_encoding(Encoding::UTF_8).valid_encoding?
+ else
+ false
+ end
+ end
else
- self
- end
- end
-
- # Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
- # them), returns false otherwise.
- def is_utf8?
- ActiveSupport::Multibyte::Chars.consumes?(self)
- end
+ def mb_chars
+ if ActiveSupport::Multibyte.proxy_class.wants?(self)
+ ActiveSupport::Multibyte.proxy_class.new(self)
+ else
+ self
+ end
+ end
- unless '1.8.7 and later'.respond_to?(:chars)
- def chars
- ActiveSupport::Deprecation.warn('String#chars has been deprecated in favor of String#mb_chars.', caller)
- mb_chars
+ # Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
+ # them), returns false otherwise.
+ def is_utf8?
+ ActiveSupport::Multibyte::Chars.consumes?(self)
+ end
end
- end
-
</ruby>
+As you can see, +mb_chars+ is where the +proxy_class+ property comes in handy. This method will create a new instance of the configured proxy class using the instance of +String+ as a constructor argument. By default, the new +String+-like object will be an instance of the proxy class +ActiveSupport::Multibyte::Chars+. You can use +ActiveSupport::Multibyte.proxy_class=+ to set a different proxy class if you wish.
-As you can see, +mb_chars+ is where the +proxy_class+ method comes in handy. This will create a new instance of that class and pass in the +String+ object in order to make it multibyte-compatible. In this case the new +String+ object will be an instance of the +ActiveSupport::Multibyte::Chars+ class. You can use +ActiveSupport::Multibyte.proxy_class=+ to set this to be a different class if you're that way inclined.
-
-Here, +is_utf8?+ calls a +consumes+ method on the not-yet-loaded +ActiveSupport::Multibyte::Chars+ class. The keen-eye would have seen this was specified as an auto-load earlier, so that is what is going to happen if we call this method or +mb_chars+. This means that it'll require the file located at _activesupport/lib/active_support/multibyte/chars.rb_. This file includes _activesupport/lib/active_support/string/access.rb_ which defines methods such as +at+, +from+, +to+, +first+ and +last+. These methods will return parts of the string depending on what is passed to them and they are defined differently depending on if you're using Ruby 1.9 or not. The second file included is _activesupport/lib/active_support/string/behaviour.rb_ which defines a single method +acts_like_string?+ on +String+ which always returns +true+. This method is used through the +acts_like?+ method which is passed a single argument representing the downcased and symbolised version of the class you want to know if it acts like. In this case the code would be +acts_like?(:string)+.
+Here, +mb_chars+ invokes +is_utf8?+ to checks if the string can be treated as UTF-8. On 1.9, the string's +encoding+ property is checked. On 1.8, +wants?+ checks to see if +$KCODE+ is "UTF-8" and, and +consumes?+ checks whether the string can be unpacked as UTF-8 without raising an error.
-The +Chars+ class defines, along with +consumes?+, other methods such as the "spaceship" method +<=>+. This method is referenced by the methods defined in the included +Comparable+ module and will return either +-1+, +0+ or +1+ depending on if the word is before, identical or after the compared word. For example, +'é'.mb_chars <=> 'ü'.mb_chars+ returns +-1+ as e comes before u in the alphabet. Other methods are the commonly used +split+, +=~+, +insert+ and +include?+.
+The keen eye will have seen +ActiveSupport::Multibyte::Chars+ was specified as an +autoload+ earlier: _activesupport/lib/active_support/multibyte/chars.rb_ will be loaded without an explicit +require+ when we call +is_utf8+ on 1.8, or +mb_chars+ on any Ruby version. This file includes _activesupport/lib/active_support/string/access.rb_ which defines methods such as +at+, +from+, +to+, +first+ and +last+. These methods will return parts of the string depending on what is passed to them.
+The second file included is _activesupport/lib/active_support/string/behavior.rb_ which only defines +acts_like_string?+ on +String+, a method which always returns +true+. This method is used by +Object#acts_like?+, which accepts a single argument representing the downcased and symbolised version of a class, and returns true if the object's behavior is like that class. In this case the code would be +acts_like?(:string)+.
+The +Chars+ class also defines other important methods such as the "spaceship" method +<=>+, which is needed by the +Comparable+ module, in order to allow UTF-8-aware sorting.
h3. Common Includes
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index a874fa0ca7..f4ba6dd53b 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -2,7 +2,7 @@ h2. Layouts and Rendering in Rails
This guide covers the basic layout features of Action Controller and Action View. By referring to this guide, you will be able to:
-* Use the various rendering methods built in to Rails
+* Use the various rendering methods built into Rails
* Create layouts with multiple content sections
* Use partials to DRY up your views
* Use nested layouts (sub-templates)
diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile
index a12434a95b..e853ba79e9 100644
--- a/railties/guides/source/plugins.textile
+++ b/railties/guides/source/plugins.textile
@@ -1284,7 +1284,7 @@ class YaffleMigrationGenerator < Rails::Generator::NamedBase
end
</ruby>
-The generator creates a new file in 'db/migrate' with a timestamp and an 'add_column' statement. It reuses the built in rails +migration_template+ method, and reuses the built-in rails migration template.
+The generator creates a new file in 'db/migrate' with a timestamp and an 'add_column' statement. It reuses the built-in rails +migration_template+ method, and reuses the built-in rails migration template.
It's courteous to check to see if table names are being pluralized whenever you create a generator that needs to be aware of table names. This way people using your generator won't have to manually change the generated files if they've turned pluralization off.
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 9ff06856c3..72a76e25bb 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -382,6 +382,12 @@ match ':controller/:action/:id/:user_id'
An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
+NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
+
+<ruby>
+match ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/
+</ruby>
+
h4. Static Segments
You can specify static segments when creating a route:
@@ -645,13 +651,13 @@ scope :path_names => { :new => "make" } do
end
</ruby>
-h4. Overriding the Named Helper Prefix
+h4. Prefixing the Named Route Helpers
-You can use the :name_prefix option to add a prefix to the named route helpers that Rails generates for a route. You can use this option to prevent collisions between routes using a path scope.
+You can use the +:as+ option to prefix the named route helpers that Rails generates for a route. Use this option to prevent name collisions between routes using a path scope.
<ruby>
scope "admin" do
- resources :photos, :name_prefix => "admin"
+ resources :photos, :as => "admin_photos"
end
resources :photos
@@ -659,17 +665,17 @@ resources :photos
This will provide route helpers such as +admin_photos_path+, +new_admin_photo_path+ etc.
-You could specify a name prefix to use for a group of routes in the scope:
+To prefix a group of routes, use +:as+ with +scope+:
<ruby>
-scope "admin", :name_prefix => "admin" do
+scope "admin", :as => "admin" do
resources :photos, :accounts
end
resources :photos, :accounts
</ruby>
-NOTE: The +namespace+ scope will automatically add a +:name_prefix+ as well as +:module+ and +:path+ prefixes.
+NOTE: The +namespace+ scope will automatically add +:as+ as well as +:module+ and +:path+ prefixes.
h4. Restricting the Routes Created
@@ -714,21 +720,21 @@ Rails now creates routes to the +CategoriesControlleR+.
h4. Overriding the Singular Form
-If you want to customize the singular name of the route in the named helpers, you can use the +:singular+ option.
+If you want to define the singular form of a resource, you should add additional rules to the +Inflector+.
<ruby>
-resources :teeth, :singular => "tooth"
+ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'tooth', 'teeth'
+end
</ruby>
-TIP: If you want to define the singular form of a word for your entire application, you should add additional rules to the +Inflector+ instead.
-
-h4(#nested-name-prefix). Using +:name_prefix+ in Nested Resources
+h4(#nested-names). Using +:as+ in Nested Resources
-The +:name_prefix+ option overrides the automatically-generated prefix for the parent resource in nested route helpers. For example,
+The +:as+ option overrides the automatically-generated name for the resource in nested route helpers. For example,
<ruby>
resources :magazines do
- resources :ads, :name_prefix => 'periodical'
+ resources :ads, :as => 'periodical_ads'
end
</ruby>
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index b45514f66d..60108d5ab3 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -670,7 +670,7 @@ Also, the second query renames some columns with the AS statement so that the we
h5(#sql-injection-countermeasures). Countermeasures
-Ruby on Rails has a built in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. <em class="highlight">Using +Model.find(id)+ or +Model.find_by_some thing(something)+ automatically applies this countermeasure</em>. But in SQL fragments, especially <em class="highlight">in conditions fragments (+:conditions => "..."+), the +connection.execute()+ or +Model.find_by_sql()+ methods, it has to be applied manually</em>.
+Ruby on Rails has a built-in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. <em class="highlight">Using +Model.find(id)+ or +Model.find_by_some thing(something)+ automatically applies this countermeasure</em>. But in SQL fragments, especially <em class="highlight">in conditions fragments (+:conditions => "..."+), the +connection.execute()+ or +Model.find_by_sql()+ methods, it has to be applied manually</em>.
Instead of passing a string to the conditions option, you can pass an array to sanitize tainted strings like this:
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 4a7ed2d028..458177b954 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -8,14 +8,6 @@ module Rails
# In Rails 3.0, a Rails::Application object was introduced which is nothing more than
# an Engine but with the responsibility of coordinating the whole boot process.
#
- # Opposite to Rails::Engine, you can only have one Rails::Application instance
- # in your process and both Rails::Application and YourApplication::Application
- # points to it.
- #
- # In other words, Rails::Application is Singleton and whenever you are accessing
- # Rails::Application.config or YourApplication::Application.config, you are actually
- # accessing YourApplication::Application.instance.config.
- #
# == Initialization
#
# Rails::Application is responsible for executing all railties, engines and plugin
@@ -57,6 +49,10 @@ module Rails
def instance
if self == Rails::Application
+ if Rails.application
+ ActiveSupport::Deprecation.warn "Calling a method in Rails::Application is deprecated, " <<
+ "please call it directly in your application constant #{Rails.application.class.name}.", caller
+ end
Rails.application
else
@@instance ||= new
@@ -125,14 +121,13 @@ module Rails
end
def reload_routes!
- routes = Rails::Application.routes
- routes.disable_clear_and_finalize = true
-
- routes.clear!
+ _routes = self.routes
+ _routes.disable_clear_and_finalize = true
+ _routes.clear!
routes_reloader.paths.each { |path| load(path) }
- ActiveSupport.on_load(:action_controller) { routes.finalize! }
+ ActiveSupport.on_load(:action_controller) { _routes.finalize! }
ensure
- routes.disable_clear_and_finalize = false
+ _routes.disable_clear_and_finalize = false
end
def initialize!
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index aad1170b56..b9353ba336 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -13,27 +13,27 @@ command = aliases[command] || command
case command
when 'generate', 'destroy', 'plugin', 'benchmarker', 'profiler'
require APP_PATH
- Rails::Application.require_environment!
+ Rails.application.require_environment!
require "rails/commands/#{command}"
when 'console'
require 'rails/commands/console'
require APP_PATH
- Rails::Application.require_environment!
- Rails::Console.start(Rails::Application)
+ Rails.application.require_environment!
+ Rails::Console.start(Rails.application)
when 'server'
require 'rails/commands/server'
Rails::Server.new.tap { |server|
require APP_PATH
- Dir.chdir(Rails::Application.root)
+ Dir.chdir(Rails.application.root)
server.start
}
when 'dbconsole'
require 'rails/commands/dbconsole'
require APP_PATH
- Rails::DBConsole.start(Rails::Application)
+ Rails::DBConsole.start(Rails.application)
when 'application', 'runner'
require "rails/commands/#{command}"
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index a3a5aed399..47c6752ca3 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -4,7 +4,7 @@ if %w(--version -v).include? ARGV.first
exit(0)
end
-if ARGV.first != "new" || ARGV.empty?
+if ARGV.first != "new"
ARGV[0] = "--help"
else
ARGV.shift
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
index 8bcd92a33b..96b6f9c372 100644
--- a/railties/lib/rails/commands/plugin.rb
+++ b/railties/lib/rails/commands/plugin.rb
@@ -3,7 +3,7 @@
# Installing plugins:
#
# $ rails plugin install continuous_builder asset_timestamping
-#
+#
# Specifying revisions:
#
# * Subversion revision is a single integer.
@@ -14,12 +14,11 @@
# 'tag 1.8.0' (equivalent to 'refs/tags/1.8.0')
#
#
-# This is Free Software, copyright 2005 by Ryan Tomayko (rtomayko@gmail.com)
+# This is Free Software, copyright 2005 by Ryan Tomayko (rtomayko@gmail.com)
# and is licensed MIT: (http://www.opensource.org/licenses/mit-license.php)
$verbose = false
-
require 'open-uri'
require 'fileutils'
require 'tempfile'
@@ -40,18 +39,18 @@ class RailsEnvironment
dir = File.dirname(dir)
end
end
-
+
def self.default
@default ||= find
end
-
+
def self.default=(rails_env)
@default = rails_env
end
-
+
def install(name_uri_or_plugin)
if name_uri_or_plugin.is_a? String
- if name_uri_or_plugin =~ /:\/\//
+ if name_uri_or_plugin =~ /:\/\//
plugin = Plugin.new(name_uri_or_plugin)
else
plugin = Plugins[name_uri_or_plugin]
@@ -65,7 +64,7 @@ class RailsEnvironment
puts "Plugin not found: #{name_uri_or_plugin}"
end
end
-
+
def use_svn?
require 'active_support/core_ext/kernel'
silence_stderr {`svn --version` rescue nil}
@@ -97,7 +96,7 @@ class RailsEnvironment
ext = `svn propget svn:externals "#{root}/vendor/plugins"`
lines = ext.respond_to?(:lines) ? ext.lines : ext
lines.reject{ |line| line.strip == '' }.map do |line|
- line.strip.split(/\s+/, 2)
+ line.strip.split(/\s+/, 2)
end
end
@@ -111,38 +110,37 @@ class RailsEnvironment
system("svn propset -q svn:externals -F \"#{file.path}\" \"#{root}/vendor/plugins\"")
end
end
-
end
class Plugin
attr_reader :name, :uri
-
+
def initialize(uri, name = nil)
@uri = uri
guess_name(uri)
end
-
+
def self.find(name)
new(name)
end
-
+
def to_s
"#{@name.ljust(30)}#{@uri}"
end
-
+
def svn_url?
@uri =~ /svn(?:\+ssh)?:\/\/*/
end
-
+
def git_url?
@uri =~ /^git:\/\// || @uri =~ /\.git$/
end
-
+
def installed?
File.directory?("#{rails_env.root}/vendor/plugins/#{name}") \
or rails_env.externals.detect{ |name, repo| self.uri == repo }
end
-
+
def install(method=nil, options = {})
method ||= rails_env.best_install_method?
if :http == method
@@ -173,7 +171,7 @@ class Plugin
if rails_env.use_externals?
# clean up svn:externals
externals = rails_env.externals
- externals.reject!{|n,u| name == n or name == u}
+ externals.reject!{|n, u| name == n or name == u}
rails_env.externals = externals
end
end
@@ -192,7 +190,7 @@ class Plugin
FileUtils.rm_rf tmp if svn_url?
end
- private
+ private
def run_install_hook
install_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/install.rb"
@@ -207,11 +205,11 @@ class Plugin
def install_using_export(options = {})
svn_command :export, options
end
-
+
def install_using_checkout(options = {})
svn_command :checkout, options
end
-
+
def install_using_externals(options = {})
externals = rails_env.externals
externals.push([@name, uri])
@@ -229,7 +227,7 @@ class Plugin
fetcher.fetch
end
end
-
+
def install_using_git(options = {})
root = rails_env.root
mkdir_p(install_path = "#{root}/vendor/plugins/#{name}")
@@ -268,7 +266,7 @@ class Plugin
end
@name.gsub!(/\.git$/, '') if @name =~ /\.git$/
end
-
+
def rails_env
@rails_env || RailsEnvironment.default
end
@@ -277,45 +275,44 @@ end
# load default environment and parse arguments
require 'optparse'
module Commands
-
class Plugin
attr_reader :environment, :script_name, :sources
def initialize
@environment = RailsEnvironment.default
@rails_root = RailsEnvironment.default.root
- @script_name = File.basename($0)
+ @script_name = File.basename($0)
@sources = []
end
-
+
def environment=(value)
@environment = value
RailsEnvironment.default = value
end
-
+
def options
OptionParser.new do |o|
o.set_summary_indent(' ')
o.banner = "Usage: plugin [OPTIONS] command"
o.define_head "Rails plugin manager."
-
- o.separator ""
+
+ o.separator ""
o.separator "GENERAL OPTIONS"
-
+
o.on("-r", "--root=DIR", String,
"Set an explicit rails app directory.",
"Default: #{@rails_root}") { |rails_root| @rails_root = rails_root; self.environment = RailsEnvironment.new(@rails_root) }
o.on("-s", "--source=URL1,URL2", Array,
"Use the specified plugin repositories instead of the defaults.") { |sources| @sources = sources}
-
+
o.on("-v", "--verbose", "Turn on verbose output.") { |verbose| $verbose = verbose }
o.on("-h", "--help", "Show this help message.") { puts o; exit }
-
+
o.separator ""
o.separator "COMMANDS"
-
+
o.separator " install Install plugin(s) from known repositories or URLs."
o.separator " remove Uninstall plugins."
-
+
o.separator ""
o.separator "EXAMPLES"
o.separator " Install a plugin:"
@@ -328,41 +325,41 @@ module Commands
o.separator " #{@script_name} plugin install -x continuous_builder\n"
end
end
-
+
def parse!(args=ARGV)
general, sub = split_args(args)
options.parse!(general)
-
+
command = general.shift
if command =~ /^(install|remove)$/
command = Commands.const_get(command.capitalize).new(self)
command.parse!(sub)
else
- puts "Unknown command: #{command}"
+ puts "Unknown command: #{command}" unless command.blank?
puts options
exit 1
end
end
-
+
def split_args(args)
left = []
left << args.shift while args[0] and args[0] =~ /^-/
left << args.shift if args[0]
- return [left, args]
+ [left, args]
end
-
+
def self.parse!(args=ARGV)
Plugin.new.parse!(args)
end
end
-
+
class Install
def initialize(base_command)
@base_command = base_command
@method = :http
@options = { :quiet => false, :revision => nil, :force => false }
end
-
+
def options
OptionParser.new do |o|
o.set_summary_indent(' ')
@@ -370,8 +367,8 @@ module Commands
o.define_head "Install one or more plugins."
o.separator ""
o.separator "Options:"
- o.on( "-x", "--externals",
- "Use svn:externals to grab the plugin.",
+ o.on( "-x", "--externals",
+ "Use svn:externals to grab the plugin.",
"Enables plugin updates and plugin versioning.") { |v| @method = :externals }
o.on( "-o", "--checkout",
"Use svn checkout to grab the plugin.",
@@ -392,7 +389,7 @@ module Commands
o.separator "a plugin repository."
end
end
-
+
def determine_install_method
best = @base_command.environment.best_install_method
@method = :http if best == :http and @method == :export
@@ -410,9 +407,13 @@ module Commands
end
@method
end
-
+
def parse!(args)
options.parse!(args)
+ if args.blank?
+ puts options
+ exit 1
+ end
environment = @base_command.environment
install_method = determine_install_method
puts "Plugins will be installed using #{install_method}" if $verbose
@@ -430,7 +431,7 @@ module Commands
def initialize(base_command)
@base_command = base_command
end
-
+
def options
OptionParser.new do |o|
o.set_summary_indent(' ')
@@ -438,9 +439,13 @@ module Commands
o.define_head "Remove plugins."
end
end
-
+
def parse!(args)
options.parse!(args)
+ if args.blank?
+ puts options
+ exit 1
+ end
root = @base_command.environment.root
args.each do |name|
::Plugin.new(name).uninstall
@@ -470,7 +475,7 @@ module Commands
end
end
end
-
+
class RecursiveHTTPFetcher
attr_accessor :quiet
def initialize(urls_to_fetch, level = 1, cwd = ".")
@@ -511,7 +516,7 @@ class RecursiveHTTPFetcher
end
links
end
-
+
def download(link)
puts "+ #{File.join(@cwd, File.basename(link))}" unless @quiet
open(link) do |stream|
@@ -520,13 +525,13 @@ class RecursiveHTTPFetcher
end
end
end
-
+
def fetch(links = @urls_to_fetch)
links.each do |l|
(l =~ /\/$/ || links == @urls_to_fetch) ? fetch_dir(l) : download(l)
end
end
-
+
def fetch_dir(url)
@level += 1
push_d(File.basename(url)) if @level > 0
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index b97ff086b6..c43a233bc3 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -37,7 +37,7 @@ ARGV.delete(code_or_file)
ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
-Rails::Application.require_environment!
+Rails.application.require_environment!
begin
if code_or_file.nil?
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 9d9dd48ea9..c3927b6613 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -21,6 +21,9 @@ module Rails
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
+ opts.on("-P","--pid=pid",String,
+ "Specifies the PID file.",
+ "Default: tmp/pids/server.pid") { |v| options[:pid] = v }
opts.separator ""
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 199afbdc30..a27d38e23a 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -267,7 +267,7 @@ module Rails
ActiveSupport::Deprecation.warn "freeze! is deprecated since your rails app now comes bundled with Rails by default, please check your Gemfile"
end
- # Make an entry in Rails routing file conifg/routes.rb
+ # Make an entry in Rails routing file config/routes.rb
#
# === Example
#
diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README
index e2764dee03..6966fe987e 100644
--- a/railties/lib/rails/generators/rails/app/templates/README
+++ b/railties/lib/rails/generators/rails/app/templates/README
@@ -35,7 +35,7 @@ link:files/vendor/rails/actionpack/README.html.
<tt>cd myapp; rails server</tt> (run with --help for options)
3. Go to http://localhost:3000/ and you'll see:
- "Welcome aboard: You're riding the Rails!"
+ "Welcome aboard: You're riding Ruby on Rails!"
4. Follow the guidelines to start developing your application. You can find
the following resources handy:
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 13f1f9fa41..d83cafc3be 100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -4,4 +4,4 @@
require File.expand_path('../config/application', __FILE__)
require 'rake'
-Rails::Application.load_tasks
+<%= app_const %>.load_tasks
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 031466cb86..67a38ea1d5 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -21,14 +21,14 @@ module <%= app_const_base %>
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
- # Add additional load paths for your own custom dirs
+ # Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W( #{config.root}/extras )
# Only load the plugins named here, in the order given (default is alphabetical).
- # :all can be used as a placeholder for all plugins not explicitly named
+ # :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
- # Activate observers that should always be running
+ # Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
index 22aa576f5d..a3143f1346 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -4,4 +4,4 @@
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-Rails.application.config.secret_token = '<%= app_secret %>'
+<%= app_const %>.config.secret_token = '<%= app_secret %>'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
index a869a21e2c..0aed79bb70 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -1,8 +1,8 @@
# Be sure to restart your server when you modify this file.
-Rails.application.config.session_store :cookie_store, :key => '_<%= app_name %>_session'
+<%= app_const %>.config.session_store :cookie_store, :key => '_<%= app_name %>_session'
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rake db:sessions:create")
-# Rails.application.config.session_store :active_record_store
+# <%= app_const %>.config.session_store :active_record_store
diff --git a/railties/lib/rails/info_routes.rb b/railties/lib/rails/info_routes.rb
index bd58034d8f..b5c4e4c1e0 100644
--- a/railties/lib/rails/info_routes.rb
+++ b/railties/lib/rails/info_routes.rb
@@ -1,3 +1,3 @@
-Rails.application.routes.draw do |map|
+Rails.application.routes.draw do
match '/rails/info/properties' => "rails/info#properties"
end
diff --git a/railties/lib/rails/tasks/middleware.rake b/railties/lib/rails/tasks/middleware.rake
index e670989345..cd35ffb19a 100644
--- a/railties/lib/rails/tasks/middleware.rake
+++ b/railties/lib/rails/tasks/middleware.rake
@@ -3,5 +3,5 @@ task :middleware => :environment do
Rails.configuration.middleware.each do |middleware|
puts "use #{middleware.inspect}"
end
- puts "run #{Rails::Application.instance.class.name}.routes"
+ puts "run #{Rails.application.class.name}.routes"
end
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 41619bc1f8..a99232c4be 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -1,6 +1,6 @@
desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task :routes => :environment do
- Rails::Application.reload_routes!
+ Rails.application.reload_routes!
all_routes = ENV['CONTROLLER'] ? Rails.application.routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : Rails.application.routes.routes
routes = all_routes.collect do |route|
# TODO: The :index method is deprecated in 1.9 in favor of :key
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 99537d6d24..247b926af9 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
s.has_rdoc = false
s.add_dependency('rake', '>= 0.8.3')
- s.add_dependency('thor', '~> 0.13.6')
+ s.add_dependency('thor', '~> 0.13.7')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 7269a7c5a8..4ff10091b1 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -36,7 +36,7 @@ module ApplicationTests
test "allows me to configure default url options for ActionMailer" do
app_file "config/environments/development.rb", <<-RUBY
- Rails::Application.configure do
+ AppTemplate::Application.configure do
config.action_mailer.default_url_options = { :host => "test.rails" }
end
RUBY
@@ -45,6 +45,25 @@ module ApplicationTests
assert_equal "test.rails", ActionMailer::Base.default_url_options[:host]
end
+ test "does not include url helpers as action methods" do
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get "/foo", :to => lambda { |env| [200, {}, []] }, :as => :foo
+ end
+ RUBY
+
+ app_file "app/mailers/foo.rb", <<-RUBY
+ class Foo < ActionMailer::Base
+ def notify
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+ assert Foo.method_defined?(:foo_path)
+ assert_equal ["notify"], Foo.action_methods
+ end
+
# AS
test "if there's no config.active_support.bare, all of ActiveSupport is required" do
use_frameworks []
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index a1fcba3310..4baa8a8170 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -16,7 +16,7 @@ module ApplicationTests
end
def app
- @app ||= Rails::Application
+ @app ||= Rails.application
end
def assert_fallbacks(fallbacks)
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 40fb446b16..d6e100ffe3 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -24,11 +24,11 @@ module ApplicationTests
app_file "config/environment.rb", <<-RUBY
SuperMiddleware = Struct.new(:app)
- Rails::Application.configure do
+ AppTemplate::Application.configure do
config.middleware.use SuperMiddleware
end
- Rails::Application.initialize!
+ AppTemplate::Application.initialize!
RUBY
assert_match "SuperMiddleware", Dir.chdir(app_path){ `rake middleware` }
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index d37b7649e2..07a3d94120 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -19,7 +19,7 @@ module ApplicationTests
end
def test_should_run_ruby_statement
- assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "puts User.count"` }
+ assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "puts User.count"` }
end
def test_should_run_file
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index aa76979c27..687c2d1568 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -11,7 +11,7 @@ class InfoControllerTest < ActionController::TestCase
tests Rails::InfoController
def setup
- Rails.application.routes.draw do |map|
+ Rails.application.routes.draw do
match '/rails/info/properties' => "rails/info#properties"
end
@controller.stubs(:consider_all_requests_local? => false, :local_request? => true)