aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile5
-rw-r--r--Rakefile15
-rw-r--r--actionmailer/Rakefile5
-rw-r--r--actionpack/CHANGELOG10
-rw-r--r--actionpack/Rakefile3
-rw-r--r--actionpack/lib/abstract_controller/base.rb13
-rw-r--r--actionpack/lib/action_controller/metal.rb11
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb4
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb3
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb14
-rw-r--r--[-rwxr-xr-x]actionpack/lib/action_dispatch/http/request.rb0
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb446
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb32
-rw-r--r--actionpack/lib/action_view/render/partials.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb1
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb1
-rw-r--r--actionpack/test/controller/record_identifier_test.rb23
-rw-r--r--actionpack/test/controller/render_test.rb11
-rw-r--r--actionpack/test/dispatch/routing_test.rb237
-rw-r--r--actionpack/test/fixtures/fun/games/_form.erb1
-rw-r--r--actionpack/test/template/date_helper_test.rb27
-rw-r--r--actionpack/test/template/form_helper_test.rb20
-rw-r--r--actionpack/test/template/form_options_helper_test.rb30
-rw-r--r--actionpack/test/template/test_case_test.rb8
-rw-r--r--actionpack/test/template/text_helper_test.rb89
-rw-r--r--[-rwxr-xr-x]activemodel/Rakefile3
-rw-r--r--activemodel/lib/active_model/validations.rb11
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb57
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb77
-rw-r--r--activerecord/CHANGELOG5
-rw-r--r--activerecord/RUNNING_UNIT_TESTS2
-rw-r--r--activerecord/Rakefile5
-rw-r--r--[-rwxr-xr-x]activerecord/examples/performance.rb0
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb6
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/base.rb36
-rw-r--r--activerecord/lib/active_record/callbacks.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/connection_adapters/abstract_adapter.rb0
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb7
-rw-r--r--activerecord/lib/active_record/observer.rb3
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb92
-rw-r--r--activerecord/lib/active_record/transactions.rb1
-rw-r--r--activerecord/lib/active_record/validations.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb12
-rw-r--r--activerecord/test/cases/adapter_test.rb15
-rw-r--r--activerecord/test/cases/autosave_association_test.rb1
-rw-r--r--[-rwxr-xr-x]activerecord/test/cases/base_test.rb4
-rw-r--r--[-rwxr-xr-x]activerecord/test/cases/counter_cache_test.rb0
-rw-r--r--activerecord/test/cases/migration_test.rb2
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb21
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb14
-rw-r--r--activerecord/test/models/pirate.rb4
-rw-r--r--activeresource/Rakefile5
-rw-r--r--activesupport/CHANGELOG4
-rw-r--r--activesupport/Rakefile4
-rw-r--r--[-rwxr-xr-x]activesupport/bin/generate_tables0
-rw-r--r--activesupport/lib/active_support.rb3
-rw-r--r--activesupport/lib/active_support/callbacks.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb2
-rw-r--r--[-rwxr-xr-x]activesupport/lib/active_support/core_ext/object/to_param.rb0
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb2
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb39
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb37
-rw-r--r--activesupport/lib/active_support/i18n.rb1
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb80
-rw-r--r--activesupport/lib/active_support/json/backends/yajl.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb34
-rw-r--r--activesupport/lib/active_support/railtie.rb72
-rw-r--r--activesupport/lib/active_support/testing/performance.rb122
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb7
-rw-r--r--activesupport/test/descendants_tracker_test.rb75
-rw-r--r--activesupport/test/file_update_checker_test.rb56
-rw-r--r--activesupport/test/multibyte_chars_test.rb14
-rwxr-xr-xbin/rails2
-rw-r--r--doc/template/horo.rb259
-rw-r--r--railties/Rakefile2
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/javascripts/code_highlighter.js0
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/javascripts/guides.js0
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/stylesheets/print.css0
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/stylesheets/reset.css0
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/stylesheets/style.css0
-rw-r--r--railties/lib/rails/application.rb32
-rw-r--r--railties/lib/rails/application/bootstrap.rb2
-rw-r--r--railties/lib/rails/application/routes_reloader.rb46
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/commands/generate.rb0
-rw-r--r--railties/lib/rails/commands/server.rb3
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/new.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb2
-rw-r--r--railties/lib/rails/generators/named_base.rb8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README8
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/generators/rails/app/templates/Rakefile0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml60
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb2
-rw-r--r--railties/test/application/initializers/i18n_test.rb133
-rw-r--r--railties/test/application/initializers/load_path_test.rb2
-rw-r--r--railties/test/application/loading_test.rb73
-rw-r--r--railties/test/application/model_initialization_test.rb33
-rw-r--r--railties/test/application/rake_test.rb2
-rw-r--r--railties/test/application/routing_test.rb14
-rw-r--r--railties/test/generators/migration_generator_test.rb15
-rw-r--r--railties/test/generators/named_base_test.rb10
-rw-r--r--railties/test/railties/i18n_railtie_test.rb89
117 files changed, 1885 insertions, 954 deletions
diff --git a/Gemfile b/Gemfile
index 19e532bc0b..fe6ef9aa8a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,6 +5,7 @@ gem "rails", :path => File.dirname(__FILE__)
gem "rake", ">= 0.8.7"
gem "mocha", ">= 0.9.8"
+gem "rdoc", "2.2"
mri = !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
if mri && RUBY_VERSION < '1.9'
@@ -44,10 +45,6 @@ elsif RUBY_ENGINE == "jruby"
end
end
-group :documentation do
- gem 'rdoc', '2.1'
-end
-
if ENV['CI']
gem "nokogiri", ">= 1.4.0"
diff --git a/Rakefile b/Rakefile
index 0ee7647a12..e608af0319 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,3 +1,6 @@
+gem 'rdoc', '= 2.2'
+require 'rdoc'
+
require 'rake'
require 'rake/rdoctask'
require 'rake/gempackagetask'
@@ -68,7 +71,15 @@ Rake::RDocTask.new do |rdoc|
rdoc.options << '--charset' << 'utf-8'
rdoc.options << '--main' << 'railties/README'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : './doc/template/horo'
+ # Workaround: RDoc assumes that rdoc.template can be required, and that
+ # rdoc.template.upcase is a constant living in RDoc::Generator::HTML
+ # which holds the actual template class.
+ #
+ # We put 'doc/template' in the load path to be able to set the template
+ # to the string 'horo' and thus meet those RDoc's assumptions.
+ $:.unshift('doc/template')
+
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : 'horo'
rdoc.rdoc_files.include('railties/CHANGELOG')
rdoc.rdoc_files.include('railties/MIT-LICENSE')
@@ -117,7 +128,7 @@ end
desc "Publish API docs for Rails as a whole and for each component"
task :pdoc => :rdoc do
require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/api", "doc/rdoc").upload
+ Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/api", "doc/rdoc").upload
PROJECTS.each do |project|
system %(cd #{project} && #{$0} pdoc)
end
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 5b72843f5e..f20e7ea928 100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,4 +1,5 @@
-require 'rubygems'
+gem 'rdoc', '= 2.2'
+require 'rdoc'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
@@ -53,5 +54,5 @@ end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload
+ Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/am", "doc").upload
end
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 967bd76025..a00f63dcd4 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,15 @@
*Rails 3.0.0 [Release Candidate] (unreleased)*
+* Add shallow routes back to the new router [Diego Carrion, Andrew White]
+
+ resources :posts do
+ shallow do
+ resources :comments
+ end
+ end
+
+ You can now use comment_path for /comments/1 instead of post_comment_path for /posts/1/comments/1.
+
* Add support for multi-subdomain session by setting cookie host in session cookie so you can share session between www.example.com, example.com and user.example.com. #4818 [Guillermo Álvarez]
* Removed textilize, textilize_without_paragraph and markdown helpers. [Santiago Pastorino]
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index f3bd7dfc10..aed5278e38 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,4 +1,5 @@
-require 'rubygems'
+gem 'rdoc', '= 2.2'
+require 'rdoc'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 4d7b4019d8..8a8337858b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,4 +1,5 @@
require 'active_support/configurable'
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
module AbstractController
@@ -10,6 +11,7 @@ module AbstractController
attr_internal :action_name
include ActiveSupport::Configurable
+ extend ActiveSupport::DescendantsTracker
class << self
attr_reader :abstract
@@ -21,17 +23,6 @@ module AbstractController
@abstract = true
end
- def inherited(klass)
- ::AbstractController::Base.descendants << klass.to_s
- super
- end
-
- # A list of all descendants of AbstractController::Base. This is
- # useful for initializers which need to add behavior to all controllers.
- def descendants
- @descendants ||= []
- end
-
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 775a5002e2..159d1f0748 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -52,8 +52,7 @@ module ActionController
class Metal < AbstractController::Base
abstract!
- # :api: public
- attr_internal :params, :env
+ attr_internal :env
# Returns the last part of the controller's name, underscored, without the ending
# "Controller". For instance, MyApp::MyPostsController would return "my_posts" for
@@ -85,6 +84,14 @@ module ActionController
super
end
+ def params
+ @_params ||= request.parameters
+ end
+
+ def params=(val)
+ @_params = val
+ end
+
# Basic implementations for content_type=, location=, and headers are
# provided to reduce the dependency on the RackDelegation module
# in Renderer and Redirector.
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index 508ea6e2b7..544b4989c7 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -14,10 +14,6 @@ module ActionController
super(action, request)
end
- def params
- @_params ||= @_request.parameters
- end
-
def response_body=(body)
response.body = body if response
super
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index 7f2eb4306b..bee50a7a3b 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -11,7 +11,7 @@ module ActionController
# polymorphic_url([:admin, @article, @comment])
#
# results in:
- #
+ #
# admin_article_comment_url(@article, @comment)
#
# == Usage within the framework
@@ -166,6 +166,7 @@ module ActionController
route << RecordIdentifier.__send__("plural_class_name", record)
route = route.singularize if inflection == :singular
route << "_"
+ route << "index_" if RecordIdentifier.uncountable?(record) && inflection == :plural
end
action_prefix(options) + route + routing_type(options).to_s
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 907c369218..d20c3b64c5 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/module'
module ActionController
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
+ # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
# the view actions to a higher logical level. Example:
#
@@ -28,7 +28,7 @@ module ActionController
# end
#
# As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
- # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
+ # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
# convention and allows you to write less code if you follow it.
module RecordIdentifier
extend self
@@ -59,7 +59,7 @@ module ActionController
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
#
# dom_id(Post.find(45), :edit) # => "edit_post_45"
- def dom_id(record, prefix = nil)
+ def dom_id(record, prefix = nil)
if record_id = record_key_for_dom_id(record)
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
else
@@ -102,6 +102,14 @@ module ActionController
model_name_from_record_or_class(record_or_class).singular
end
+ # Identifies whether the class name of a record or class is uncountable. Examples:
+ #
+ # uncountable?(Sheep) # => true
+ # uncountable?(Post) => false
+ def uncountable?(record_or_class)
+ plural_class_name(record_or_class) == singular_class_name(record_or_class)
+ end
+
private
def model_name_from_record_or_class(record_or_class)
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 98f4f5ae18..98f4f5ae18 100755..100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index d07841218a..e4ae480bfb 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -8,7 +8,7 @@ module ActionDispatch
class Callbacks
include ActiveSupport::Callbacks
- define_callbacks :call, :terminator => "result == false", :rescuable => true
+ define_callbacks :call, :rescuable => true
define_callbacks :prepare, :scope => :name
# Add a preparation callback. Preparation callbacks are run before every
@@ -37,12 +37,12 @@ module ActionDispatch
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
- run_callbacks(:prepare)
+ _run_prepare_callbacks
end
def call(env)
- run_callbacks(:call) do
- run_callbacks(:prepare) if @prepare_each_request
+ _run_call_callbacks do
+ _run_prepare_callbacks if @prepare_each_request
@app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 0a6d2bfc8a..e095b51342 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -122,7 +122,7 @@ module ActionDispatch
end
def render(status, body)
- [status, {'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s}, [body]]
+ [status, {'Content-Type' => 'text/html', 'Content-Length' => body.bytesize.to_s}, [body]]
end
def public_path
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 38da44d7e7..ed93211255 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -10,7 +10,7 @@ module ActionDispatch
# Prepare dispatcher callbacks and run 'prepare' callbacks
initializer "action_dispatch.prepare_dispatcher" do |app|
- ActionDispatch::Callbacks.to_prepare { app.routes_reloader.reload_if_changed }
+ ActionDispatch::Callbacks.to_prepare { app.routes_reloader.execute_if_updated }
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 7b79b6bde3..c31f681411 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -33,7 +33,7 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor]
+ IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
def initialize(set, scope, args)
@set, @scope = set, scope
@@ -102,7 +102,7 @@ module ActionDispatch
end
def requirements
- @requirements ||= (@options[:constraints] || {}).tap do |requirements|
+ @requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements|
requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
@options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
end
@@ -343,7 +343,7 @@ module ActionDispatch
def namespace(path)
path = path.to_s
- scope(:path => path, :name_prefix => path, :module => path) { yield }
+ scope(:path => path, :name_prefix => path, :module => path, :shallow_path => path, :shallow_prefix => path) { yield }
end
def constraints(constraints = {})
@@ -378,10 +378,18 @@ module ActionDispatch
Mapper.normalize_path("#{parent}/#{child}")
end
+ def merge_shallow_path_scope(parent, child)
+ Mapper.normalize_path("#{parent}/#{child}")
+ end
+
def merge_name_prefix_scope(parent, child)
parent ? "#{parent}_#{child}" : child
end
+ def merge_shallow_prefix_scope(parent, child)
+ parent ? "#{parent}_#{child}" : child
+ end
+
def merge_module_scope(parent, child)
parent ? "#{parent}/#{child}" : child
end
@@ -409,11 +417,13 @@ module ActionDispatch
def merge_options_scope(parent, child)
(parent || {}).merge(child)
end
+
+ def merge_shallow_scope(parent, child)
+ child ? true : false
+ end
end
module Resources
- CRUD_ACTIONS = [:index, :show, :create, :update, :destroy] #:nodoc:
-
class Resource #:nodoc:
def self.default_actions
[:index, :create, :new, :show, :update, :destroy, :edit]
@@ -442,15 +452,6 @@ module ActionDispatch
end
end
- def action_type(action)
- case action
- when :index, :create
- :collection
- when :show, :update, :destroy
- :member
- end
- end
-
def name
options[:as] || @name
end
@@ -463,34 +464,19 @@ module ActionDispatch
name.to_s.singularize
end
- def member_prefix
- ':id'
- end
-
def member_name
singular
end
+ alias_method :nested_name, :member_name
+
# Checks for uncountable plurals, and appends "_index" if they're.
def collection_name
- uncountable? ? "#{plural}_index" : plural
+ singular == plural ? "#{plural}_index" : plural
end
- def uncountable?
- singular == plural
- end
-
- def name_for_action(action)
- case action_type(action)
- when :collection
- collection_name
- when :member
- member_name
- end
- end
-
- def id_segment
- ":#{singular}_id"
+ def shallow?
+ options[:shallow] ? true : false
end
def constraints
@@ -506,21 +492,43 @@ module ActionDispatch
end
def collection_options
- (options || {}).dup.tap do |options|
- options.delete(:id)
- options[:constraints] = options[:constraints].dup if options[:constraints]
- options[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
+ (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_prefix
- id_segment
+ def nested_path
+ "#{path}/:#{singular}_id"
end
def nested_options
- options = { :name_prefix => member_name }
- options["#{singular}_id".to_sym] = id_constraint if id_constraint?
- options
+ {}.tap do |opts|
+ opts[:name_prefix] = 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 }]
+ end
+
+ def collection_scope
+ [path, collection_options]
+ end
+
+ def member_scope
+ ["#{path}/:id", options]
+ end
+
+ def new_scope
+ [path]
+ end
+
+ def nested_scope
+ [nested_path, nested_options]
end
end
@@ -533,27 +541,28 @@ module ActionDispatch
super
end
- def action_type(action)
- case action
- when :show, :create, :update, :destroy
- :member
- end
+ def member_name
+ name
end
+ alias_method :collection_name, :member_name
- def member_prefix
- ''
+ def nested_path
+ path
end
- def member_name
- name
+ def nested_options
+ {}.tap do |opts|
+ opts[:name_prefix] = member_name
+ opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil?
+ end
end
- def nested_prefix
- ''
+ def shallow?
+ false
end
- def nested_options
- { :name_prefix => member_name }
+ def member_scope
+ [path, options]
end
end
@@ -565,28 +574,25 @@ module ActionDispatch
def resource(*resources, &block)
options = resources.extract_options!
options = (@scope[:options] || {}).merge(options)
+ options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
if apply_common_behavior_for(:resource, resources, options, &block)
return self
end
- resource = SingletonResource.new(resources.pop, options)
-
- scope(:path => resource.path, :controller => resource.controller) do
- with_scope_level(:resource, resource) do
+ resource_scope(SingletonResource.new(resources.pop, options)) do
+ yield if block_given?
- yield if block_given?
+ collection_scope do
+ post :create if parent_resource.actions.include?(:create)
+ get :new if parent_resource.actions.include?(:new)
+ end
- with_scope_level(:member) do
- scope(resource.options) do
- get :show if resource.actions.include?(:show)
- post :create if resource.actions.include?(:create)
- put :update if resource.actions.include?(:update)
- delete :destroy if resource.actions.include?(:destroy)
- get :new, :as => resource.name if resource.actions.include?(:new)
- get :edit, :as => resource.name if resource.actions.include?(:edit)
- end
- end
+ member_scope do
+ get :show if parent_resource.actions.include?(:show)
+ put :update if parent_resource.actions.include?(:update)
+ delete :destroy if parent_resource.actions.include?(:destroy)
+ get :edit if parent_resource.actions.include?(:edit)
end
end
@@ -596,35 +602,26 @@ module ActionDispatch
def resources(*resources, &block)
options = resources.extract_options!
options = (@scope[:options] || {}).merge(options)
+ options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
if apply_common_behavior_for(:resources, resources, options, &block)
return self
end
- resource = Resource.new(resources.pop, options)
-
- scope(:path => resource.path, :controller => resource.controller) do
- with_scope_level(:resources, resource) do
- yield if block_given?
+ resource_scope(Resource.new(resources.pop, options)) do
+ yield if block_given?
- with_scope_level(:collection) do
- scope(resource.collection_options) do
- get :index if resource.actions.include?(:index)
- post :create if resource.actions.include?(:create)
- get :new, :as => resource.singular if resource.actions.include?(:new)
- end
- end
+ collection_scope do
+ get :index if parent_resource.actions.include?(:index)
+ post :create if parent_resource.actions.include?(:create)
+ get :new if parent_resource.actions.include?(:new)
+ end
- with_scope_level(:member) do
- scope(':id') do
- scope(resource.options) do
- get :show if resource.actions.include?(:show)
- put :update if resource.actions.include?(:update)
- delete :destroy if resource.actions.include?(:destroy)
- get :edit, :as => resource.singular if resource.actions.include?(:edit)
- end
- end
- end
+ member_scope do
+ get :show if parent_resource.actions.include?(:show)
+ put :update if parent_resource.actions.include?(:update)
+ delete :destroy if parent_resource.actions.include?(:destroy)
+ get :edit if parent_resource.actions.include?(:edit)
end
end
@@ -636,10 +633,8 @@ module ActionDispatch
raise ArgumentError, "can't use collection outside resources scope"
end
- with_scope_level(:collection) do
- scope(:name_prefix => parent_resource.collection_name, :as => "") do
- yield
- end
+ collection_scope do
+ yield
end
end
@@ -648,10 +643,8 @@ module ActionDispatch
raise ArgumentError, "can't use member outside resource(s) scope"
end
- with_scope_level(:member) do
- scope(parent_resource.member_prefix, :name_prefix => parent_resource.member_name, :as => "") do
- yield
- end
+ member_scope do
+ yield
end
end
@@ -659,10 +652,12 @@ module ActionDispatch
unless resource_scope?
raise ArgumentError, "can't use new outside resource(s) scope"
end
-
+
with_scope_level(:new) do
- scope(new_scope_prefix, :name_prefix => parent_resource.member_name, :as => "") do
- yield
+ scope(*parent_resource.new_scope) do
+ scope(action_path(:new)) do
+ yield
+ end
end
end
end
@@ -673,8 +668,18 @@ module ActionDispatch
end
with_scope_level(:nested) do
- scope(parent_resource.nested_prefix, parent_resource.nested_options) do
- yield
+ if parent_resource.shallow?
+ with_exclusive_scope do
+ if @scope[:shallow_path].blank?
+ scope(*parent_resource.nested_scope) { yield }
+ else
+ scope(@scope[:shallow_path], :name_prefix => @scope[:shallow_prefix]) do
+ scope(*parent_resource.nested_scope) { yield }
+ end
+ end
+ end
+ else
+ scope(*parent_resource.nested_scope) { yield }
end
end
end
@@ -687,63 +692,79 @@ module ActionDispatch
end
end
+ def shallow
+ scope(:shallow => true) do
+ yield
+ end
+ end
+
def match(*args)
options = args.extract_options!
options[:anchor] = true unless options.key?(:anchor)
if args.length > 1
- args.each { |path| match(path, options) }
+ args.each { |path| match(path, options.dup) }
return self
end
- path_names = options.delete(:path_names)
+ if [:collection, :member, :new].include?(options[:on])
+ args.push(options)
- if args.first.is_a?(Symbol)
- action = args.first
- if CRUD_ACTIONS.include?(action)
- begin
- old_path = @scope[:path]
- @scope[:path] = "#{@scope[:path]}(.:format)"
- return match(options.reverse_merge(
- :to => action,
- :as => parent_resource.name_for_action(action)
- ))
- ensure
- @scope[:path] = old_path
- end
- else
- with_exclusive_name_prefix(action_name_prefix(action, options)) do
- return match("#{action_path(action, path_names)}(.:format)", options.reverse_merge(:to => action))
- end
+ case options.delete(:on)
+ when :collection
+ return collection { match(*args) }
+ when :member
+ return member { match(*args) }
+ when :new
+ return new { match(*args) }
end
end
- args.push(options)
-
- case options.delete(:on)
- when :collection
- return collection { match(*args) }
- when :member
+ if @scope[:scope_level] == :resource
+ args.push(options)
return member { match(*args) }
- when :new
- return new { match(*args) }
end
- if @scope[:scope_level] == :resource
- return member { match(*args) }
+ path_names = options.delete(:path_names)
+
+ if args.first.is_a?(Symbol)
+ path = path_for_action(args.first, path_names)
+ options = options_for_action(args.first, options)
+
+ with_exclusive_scope do
+ return super(path, options)
+ end
+ elsif resource_method_scope?
+ path = path_for_custom_action
+ options[:as] = name_for_action(options[:as]) if options[:as]
+ args.push(options)
+
+ with_exclusive_scope do
+ scope(path) do
+ return super
+ end
+ end
end
if resource_scope?
raise ArgumentError, "can't define route directly in resource(s) scope"
end
+ args.push(options)
super
end
def root(options={})
- options[:on] ||= :collection if @scope[:scope_level] == :resources
- super(options)
+ if @scope[:scope_level] == :resources
+ with_scope_level(:nested) do
+ scope(parent_resource.path, :name_prefix => parent_resource.collection_name) do
+ super(options)
+ end
+ end
+ else
+ super(options)
+ end
end
protected
@@ -752,15 +773,6 @@ module ActionDispatch
end
private
- def action_path(name, path_names = nil)
- path_names ||= @scope[:path_names]
- path_names[name.to_sym] || name.to_s
- end
-
- def action_name_prefix(action, options = {})
- (options[:on] == :new || @scope[:scope_level] == :new) ? "#{action}_new" : action
- end
-
def apply_common_behavior_for(method, resources, options, &block)
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
@@ -784,27 +796,24 @@ module ActionDispatch
false
end
- def new_scope_prefix
- @scope[:path_names][:new] || 'new'
- end
-
def resource_scope?
[:resource, :resources].include?(@scope[:scope_level])
end
- def with_exclusive_name_prefix(prefix)
+ def resource_method_scope?
+ [:collection, :member, :new].include?(@scope[:scope_level])
+ end
+
+ def with_exclusive_scope
begin
- old_name_prefix = @scope[:name_prefix]
+ old_name_prefix, old_path = @scope[:name_prefix], @scope[:path]
+ @scope[:name_prefix], @scope[:path] = nil, nil
- if !old_name_prefix.blank?
- @scope[:name_prefix] = "#{prefix}_#{@scope[:name_prefix]}"
- else
- @scope[:name_prefix] = prefix.to_s
+ with_scope_level(:exclusive) do
+ yield
end
-
- yield
ensure
- @scope[:name_prefix] = old_name_prefix
+ @scope[:name_prefix], @scope[:path] = old_name_prefix, old_path
end
end
@@ -816,6 +825,125 @@ module ActionDispatch
@scope[:scope_level] = old
@scope[:scope_level_resource] = old_resource
end
+
+ def resource_scope(resource)
+ with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
+ scope(*parent_resource.resource_scope) do
+ yield
+ end
+ end
+ end
+
+ def collection_scope
+ with_scope_level(:collection) do
+ scope(*parent_resource.collection_scope) do
+ yield
+ end
+ end
+ end
+
+ def member_scope
+ with_scope_level(:member) do
+ scope(*parent_resource.member_scope) do
+ yield
+ end
+ end
+ end
+
+ def path_for_action(action, path_names)
+ case action
+ when :index, :create
+ "#{@scope[:path]}(.:format)"
+ when :show, :update, :destroy
+ if parent_resource.shallow?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id(.:format)"
+ else
+ "#{@scope[:path]}(.:format)"
+ end
+ when :new
+ "#{@scope[:path]}/#{action_path(:new)}(.:format)"
+ when :edit
+ if parent_resource.shallow?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(:edit)}(.:format)"
+ else
+ "#{@scope[:path]}/#{action_path(:edit)}(.:format)"
+ end
+ else
+ case @scope[:scope_level]
+ when :collection, :new
+ "#{@scope[:path]}/#{action_path(action)}(.:format)"
+ else
+ if parent_resource.shallow?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(action)}(.:format)"
+ else
+ "#{@scope[:path]}/#{action_path(action)}(.:format)"
+ end
+ end
+ end
+ end
+
+ def path_for_custom_action
+ case @scope[:scope_level]
+ when :collection, :new
+ @scope[:path]
+ else
+ if parent_resource.shallow?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id"
+ else
+ @scope[:path]
+ end
+ end
+ end
+
+ def action_path(name, path_names = nil)
+ path_names ||= @scope[:path_names]
+ path_names[name.to_sym] || name.to_s
+ end
+
+ def options_for_action(action, options)
+ options.reverse_merge(
+ :to => action,
+ :as => name_for_action(action)
+ )
+ end
+
+ def name_for_action(action)
+ name_prefix = @scope[:name_prefix].blank? ? "" : "#{@scope[:name_prefix]}_"
+ shallow_prefix = @scope[:shallow_prefix].blank? ? "" : "#{@scope[:shallow_prefix]}_"
+
+ case action
+ when :index, :create
+ "#{name_prefix}#{parent_resource.collection_name}"
+ when :show, :update, :destroy
+ if parent_resource.shallow?
+ "#{shallow_prefix}#{parent_resource.member_name}"
+ else
+ "#{name_prefix}#{parent_resource.member_name}"
+ end
+ when :edit
+ if parent_resource.shallow?
+ "edit_#{shallow_prefix}#{parent_resource.member_name}"
+ else
+ "edit_#{name_prefix}#{parent_resource.member_name}"
+ end
+ when :new
+ "new_#{name_prefix}#{parent_resource.member_name}"
+ else
+ case @scope[:scope_level]
+ when :collection
+ "#{action}_#{name_prefix}#{parent_resource.collection_name}"
+ when :new
+ "#{action}_new_#{name_prefix}#{parent_resource.member_name}"
+ else
+ if parent_resource.shallow?
+ "#{action}_#{shallow_prefix}#{parent_resource.member_name}"
+ else
+ "#{action}_#{name_prefix}#{parent_resource.member_name}"
+ end
+ end
+ end
+ end
+
end
include Base
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6f387bc95a..7d7b6a1d91 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -896,8 +896,10 @@ module ActionView
# Returns the separator for a given datetime component
def separator(type)
case type
- when :month, :day
- @options[:date_separator]
+ when :month
+ @options[:discard_month] ? "" : @options[:date_separator]
+ when :day
+ @options[:discard_day] ? "" : @options[:date_separator]
when :hour
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
when :minute
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index a8887a804e..8efed98bd2 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -303,7 +303,7 @@ module ActionView
args.unshift object
end
- options[:html][:remote] = true if options.delete(:remote)
+ (options[:html] ||= {})[:remote] = true if options.delete(:remote)
output = form_tag(options.delete(:url) || {}, options.delete(:html) || {})
output << fields_for(object_name, *(args << options), &proc)
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index c564d30302..6f9d14de8b 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -399,7 +399,7 @@ module ActionView
options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
options_for_select += '</optgroup>'
- end
+ end.html_safe
end
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 9afa989453..0be8a2c36e 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -40,7 +40,10 @@ module ActionView
# for a total length not exceeding <tt>:length</tt>.
#
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
- # Pass a <tt>:safe</tt> value as "true" to not to escape the content.
+ #
+ # The result is not marked as HTML-safe, so will be subject to the default escaping when
+ # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags
+ # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags).
#
# ==== Examples
#
@@ -57,12 +60,6 @@ module ActionView
# # => "And they f... (continued)"
#
# truncate("<p>Once upon a time in a world far far away</p>")
- # # => "&lt;p&gt;Once upon a time i..."
- #
- # truncate("<p>Once upon a time in a world far far away</p>", :safe => true)
- # # => "<p>Once upon a time in a wo..."
- #
- # truncate("<p>Once upon a time in a world far far away</p>".html_safe)
# # => "<p>Once upon a time in a wo..."
#
# You can still use <tt>truncate</tt> with the old API that accepts the
@@ -85,7 +82,6 @@ module ActionView
options.reverse_merge!(:length => 30)
- text = h(text) unless text.html_safe? || options[:safe]
text.truncate(options.delete(:length), options) if text
end
@@ -117,13 +113,13 @@ module ActionView
end
options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
- text = h(text) unless text.html_safe? || options[:safe]
+ text = sanitize(text) unless options[:sanitize] == false
if text.blank? || phrases.blank?
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
text.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter])
- end
+ end.html_safe
end
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
@@ -253,9 +249,9 @@ module ActionView
# simple_format("Look ma! A class!", :class => 'description')
# # => "<p class='description'>Look ma! A class!</p>"
def simple_format(text, html_options={}, options={})
- text = '' if text.nil?
+ text = ''.html_safe if text.nil?
start_tag = tag('p', html_options, true)
- text = h(text) unless text.html_safe? || options[:safe]
+ text = sanitize(text) unless options[:sanitize] == false
text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
@@ -499,7 +495,11 @@ module ActionView
link_text = block_given?? yield(href) : href
href = 'http://' + href unless scheme
- content_tag(:a, link_text, link_attributes.merge('href' => href), !(options[:safe] || text.html_safe?)) + punctuation.reverse.join('')
+ unless options[:sanitize] == false
+ link_text = sanitize(link_text)
+ href = sanitize(href)
+ end
+ content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('')
end
end.html_safe
end
@@ -514,7 +514,11 @@ module ActionView
text.html_safe
else
display_text = (block_given?) ? yield(text) : text
- display_text = h(display_text) unless options[:safe]
+
+ unless options[:sanitize] == false
+ text = sanitize(text)
+ display_text = sanitize(display_text) unless text == display_text
+ end
mail_to text, display_text, html_options
end
end
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index a2c191c580..459aae94a2 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -318,7 +318,7 @@ module ActionView
object.class.model_name.partial_path.dup.tap do |partial|
path = @view.controller_path
- partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/)
+ partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
end
end
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index dc7f901f07..b698b4cfec 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -85,6 +85,7 @@ module ActionView
def setup_with_controller
@controller = ActionView::TestCase::TestController.new
+ @request = @controller.request
@output_buffer = ActiveSupport::SafeBuffer.new
@rendered = ''
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index 9f5e8ec657..6e1e6cdd20 100644
--- a/actionpack/test/activerecord/polymorphic_routes_test.rb
+++ b/actionpack/test/activerecord/polymorphic_routes_test.rb
@@ -381,6 +381,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
with_test_routes do
@series.save
assert_equal "http://example.com/series/#{@series.id}", polymorphic_url(@series)
+ assert_equal "http://example.com/series", polymorphic_url(Series.new)
end
end
diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb
index 813dedc80d..6a84475758 100644
--- a/actionpack/test/controller/record_identifier_test.rb
+++ b/actionpack/test/controller/record_identifier_test.rb
@@ -13,6 +13,19 @@ class Comment
end
end
+class Sheep
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ attr_reader :id
+ def to_key; id ? [id] : nil end
+ def save; @id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new sheep' : "sheep ##{@id}"
+ end
+end
+
class Comment::Nested < Comment; end
class Test::Unit::TestCase
@@ -20,7 +33,7 @@ class Test::Unit::TestCase
def comments_url
'http://www.example.com/comments'
end
-
+
def comment_url(comment)
"http://www.example.com/comments/#{comment.id}"
end
@@ -35,6 +48,7 @@ class RecordIdentifierTest < Test::Unit::TestCase
@record = @klass.new
@singular = 'comment'
@plural = 'comments'
+ @uncountable = Sheep
end
def test_dom_id_with_new_record
@@ -58,7 +72,7 @@ class RecordIdentifierTest < Test::Unit::TestCase
def test_dom_class
assert_equal @singular, dom_class(@record)
end
-
+
def test_dom_class_with_prefix
assert_equal "custom_prefix_#{@singular}", dom_class(@record, :custom_prefix)
end
@@ -79,6 +93,11 @@ class RecordIdentifierTest < Test::Unit::TestCase
assert_equal @plural, plural_class_name(@klass)
end
+ def test_uncountable
+ assert_equal true, uncountable?(@uncountable)
+ assert_equal false, uncountable?(@klass)
+ end
+
private
def method_missing(method, *args)
RecordIdentifier.send(method, *args)
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index e3ed097c67..a57a12f271 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -7,6 +7,10 @@ module Fun
# :ported:
def hello_world
end
+
+ def nested_partial_with_form_builder
+ render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}, Proc.new {})
+ end
end
end
@@ -1230,6 +1234,13 @@ class RenderTest < ActionController::TestCase
assert_match(/<label/, @response.body)
assert_template('test/_labelling_form')
end
+
+ def test_nested_partial_with_form_builder
+ @controller = Fun::GamesController.new
+ get :nested_partial_with_form_builder
+ assert_match(/<label/, @response.body)
+ assert_template('fun/games/_form')
+ end
def test_partial_collection
get :partial_collection
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index e13960e0dc..495255c22b 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -68,6 +68,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get 'admin/accounts' => "queenbee#accounts"
end
+ get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor
+
scope 'pt', :name_prefix => 'pt' do
resources :projects, :path_names => { :edit => 'editar', :new => 'novo' }, :path => 'projetos' do
post :preview, :on => :new
@@ -142,6 +144,32 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :comments, :except => :destroy
end
+ resource :past, :only => :destroy
+ resource :present, :only => :update
+ resource :future, :only => :create
+ resources :relationships, :only => [:create, :destroy]
+ resources :friendships, :only => [:update]
+
+ shallow do
+ namespace :api do
+ resources :teams do
+ resources :players
+ resource :captain
+ end
+ end
+ end
+
+ resources :threads, :shallow => true do
+ resource :owner
+ resources :messages do
+ resources :comments do
+ member do
+ post :preview
+ end
+ end
+ end
+ end
+
resources :sheep
resources :clients do
@@ -154,6 +182,33 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ resources :customers do
+ get "recent" => "customers#recent", :as => :recent, :on => :collection
+ get "profile" => "customers#profile", :as => :profile, :on => :member
+ post "preview" => "customers#preview", :as => :preview, :on => :new
+ resource :avatar do
+ get "thumbnail(.:format)" => "avatars#thumbnail", :as => :thumbnail, :on => :member
+ end
+ resources :invoices do
+ get "outstanding" => "invoices#outstanding", :as => :outstanding, :on => :collection
+ get "overdue", :to => :overdue, :on => :collection
+ get "print" => "invoices#print", :as => :print, :on => :member
+ post "preview" => "invoices#preview", :as => :preview, :on => :new
+ end
+ resources :notes, :shallow => true do
+ get "preview" => "notes#preview", :as => :preview, :on => :new
+ get "print" => "notes#print", :as => :print, :on => :member
+ end
+ end
+
+ namespace :api do
+ resources :customers do
+ get "recent" => "customers#recent", :as => :recent, :on => :collection
+ get "profile" => "customers#profile", :as => :profile, :on => :member
+ post "preview" => "customers#preview", :as => :preview, :on => :new
+ end
+ end
+
match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
match 'people/:id/update', :to => 'people#update', :as => :update_person
@@ -223,8 +278,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
- scope :module => 'api' do
+ scope :module => :api do
resource :token
+ resources :errors, :shallow => true do
+ resources :notices
+ end
end
scope :path => 'api' do
@@ -448,6 +506,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
assert_equal 'pass', @response.headers['X-Cascade']
+
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#passwords', @response.body
+
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
end
end
@@ -709,6 +773,38 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_resource_routes_only_create_update_destroy
+ with_test_routes do
+ delete '/past'
+ assert_equal 'pasts#destroy', @response.body
+ assert_equal '/past', past_path
+
+ put '/present'
+ assert_equal 'presents#update', @response.body
+ assert_equal '/present', present_path
+
+ post '/future'
+ assert_equal 'futures#create', @response.body
+ assert_equal '/future', future_path
+ end
+ end
+
+ def test_resources_routes_only_create_update_destroy
+ with_test_routes do
+ post '/relationships'
+ assert_equal 'relationships#create', @response.body
+ assert_equal '/relationships', relationships_path
+
+ delete '/relationships/1'
+ assert_equal 'relationships#destroy', @response.body
+ assert_equal '/relationships/1', relationship_path(1)
+
+ put '/friendships/1'
+ assert_equal 'friendships#update', @response.body
+ assert_equal '/friendships/1', friendship_path(1)
+ end
+ end
+
def test_resource_with_slugs_in_ids
with_test_routes do
get '/posts/rails-rocks'
@@ -823,7 +919,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/account/admin/subscription', account_admin_subscription_path
end
end
-
+
def test_namespace_nested_in_resources
with_test_routes do
get '/clients/1/google/account'
@@ -1132,6 +1228,143 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_shallow_nested_resources
+ with_test_routes do
+
+ get '/api/teams'
+ assert_equal 'api/teams#index', @response.body
+ assert_equal '/api/teams', api_teams_path
+
+ get '/api/teams/new'
+ assert_equal 'api/teams#new', @response.body
+ assert_equal '/api/teams/new', new_api_team_path
+
+ get '/api/teams/1'
+ assert_equal 'api/teams#show', @response.body
+ assert_equal '/api/teams/1', api_team_path(:id => '1')
+
+ get '/api/teams/1/edit'
+ assert_equal 'api/teams#edit', @response.body
+ assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1')
+
+ get '/api/teams/1/players'
+ assert_equal 'api/players#index', @response.body
+ assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1')
+
+ get '/api/teams/1/players/new'
+ assert_equal 'api/players#new', @response.body
+ assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1')
+
+ get '/api/players/2'
+ assert_equal 'api/players#show', @response.body
+ assert_equal '/api/players/2', api_player_path(:id => '2')
+
+ get '/api/players/2/edit'
+ assert_equal 'api/players#edit', @response.body
+ assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2')
+
+ get '/api/teams/1/captain'
+ assert_equal 'api/captains#show', @response.body
+ assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1')
+
+ get '/api/teams/1/captain/new'
+ assert_equal 'api/captains#new', @response.body
+ assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1')
+
+ get '/api/teams/1/captain/edit'
+ assert_equal 'api/captains#edit', @response.body
+ assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1')
+
+ get '/threads'
+ assert_equal 'threads#index', @response.body
+ assert_equal '/threads', threads_path
+
+ get '/threads/new'
+ assert_equal 'threads#new', @response.body
+ assert_equal '/threads/new', new_thread_path
+
+ get '/threads/1'
+ assert_equal 'threads#show', @response.body
+ assert_equal '/threads/1', thread_path(:id => '1')
+
+ get '/threads/1/edit'
+ assert_equal 'threads#edit', @response.body
+ assert_equal '/threads/1/edit', edit_thread_path(:id => '1')
+
+ get '/threads/1/owner'
+ assert_equal 'owners#show', @response.body
+ assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1')
+
+ get '/threads/1/messages'
+ assert_equal 'messages#index', @response.body
+ assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1')
+
+ get '/threads/1/messages/new'
+ assert_equal 'messages#new', @response.body
+ assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1')
+
+ get '/messages/2'
+ assert_equal 'messages#show', @response.body
+ assert_equal '/messages/2', message_path(:id => '2')
+
+ get '/messages/2/edit'
+ assert_equal 'messages#edit', @response.body
+ assert_equal '/messages/2/edit', edit_message_path(:id => '2')
+
+ get '/messages/2/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/messages/2/comments', message_comments_path(:message_id => '2')
+
+ get '/messages/2/comments/new'
+ assert_equal 'comments#new', @response.body
+ assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2')
+
+ get '/comments/3'
+ assert_equal 'comments#show', @response.body
+ assert_equal '/comments/3', comment_path(:id => '3')
+
+ get '/comments/3/edit'
+ assert_equal 'comments#edit', @response.body
+ assert_equal '/comments/3/edit', edit_comment_path(:id => '3')
+
+ post '/comments/3/preview'
+ assert_equal 'comments#preview', @response.body
+ assert_equal '/comments/3/preview', preview_comment_path(:id => '3')
+ end
+ end
+
+ def test_custom_resource_routes_are_scoped
+ with_test_routes do
+ assert_equal '/customers/recent', recent_customers_path
+ assert_equal '/customers/1/profile', profile_customer_path(:id => '1')
+ assert_equal '/customers/new/preview', preview_new_customer_path
+ assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg)
+ assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1')
+ assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2')
+ assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1')
+ assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1')
+ assert_equal '/notes/1/print', print_note_path(:id => '1')
+ assert_equal '/api/customers/recent', recent_api_customers_path
+ assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1')
+ assert_equal '/api/customers/new/preview', preview_new_api_customer_path
+
+ get '/customers/1/invoices/overdue'
+ assert_equal 'invoices#overdue', @response.body
+ end
+ end
+
+ def test_shallow_nested_routes_ignore_module
+ with_test_routes do
+ get '/errors/1/notices'
+ assert_equal 'api/notices#index', @response.body
+ assert_equal '/errors/1/notices', error_notices_path(:error_id => '1')
+
+ get '/notices/1'
+ assert_equal 'api/notices#show', @response.body
+ assert_equal '/notices/1', notice_path(:id => '1')
+ end
+ end
+
private
def with_test_routes
yield
diff --git a/actionpack/test/fixtures/fun/games/_form.erb b/actionpack/test/fixtures/fun/games/_form.erb
new file mode 100644
index 0000000000..01107f1cb2
--- /dev/null
+++ b/actionpack/test/fixtures/fun/games/_form.erb
@@ -0,0 +1 @@
+<%= form.label :title %>
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 053fcc4d24..a1db49d4d0 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -882,6 +882,33 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
end
+ def test_select_date_with_separator_and_discard_day
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << " / "
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" />\n)
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
+ end
+
+ def test_select_date_with_separator_discard_month_and_day
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<input type="hidden" id="date_first_month" name="date[first][month]" value="8" />\n)
+ expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="16" />\n)
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_month => true, :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
+ end
+
def test_select_datetime
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 2f3869994c..8de1e782c0 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -644,6 +644,26 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_remote_without_html
+ assert_deprecated do
+ form_for(:post, @post, :remote => true) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+ end
+
+ expected =
+ "<form action='http://www.example.com' method='post' data-remote='true'>" +
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
+ "</form>"
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_without_object
form_for(:post, :html => { :id => 'create-post' }) do |f|
concat f.text_field(:title)
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 19b73aa810..65b5f5ccc1 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -177,17 +177,16 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_option_groups_from_collection_for_select
- @continents = [
- Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
- ]
-
assert_dom_equal(
"<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
- option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk")
+ option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk")
)
end
+ def test_option_groups_from_collection_for_select_returns_html_safe_string
+ assert option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk").html_safe?
+ end
+
def test_grouped_options_for_select_with_array
assert_dom_equal(
"<optgroup label=\"North America\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>",
@@ -824,31 +823,21 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_grouped_collection_select
- @continents = [
- Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
- ]
-
@post = Post.new
@post.origin = 'dk'
assert_dom_equal(
%Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
- grouped_collection_select("post", "origin", @continents, :countries, :continent_name, :country_id, :country_name)
+ grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name)
)
end
def test_grouped_collection_select_under_fields_for
- @continents = [
- Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
- ]
-
@post = Post.new
@post.origin = 'dk'
output_buffer = fields_for :post, @post do |f|
- concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name)
+ concat f.grouped_collection_select("origin", dummy_continents, :countries, :continent_name, :country_id, :country_name)
end
assert_dom_equal(
@@ -864,4 +853,9 @@ class FormOptionsHelperTest < ActionView::TestCase
Post.new("Babe went home", "Babe", "To a little house", "shh!"),
Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ]
end
+
+ def dummy_continents
+ [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) ]
+ end
end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index 9b50ea8a42..c365aec841 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -218,4 +218,12 @@ module ActionView
end
end
end
+
+ class RenderTemplateTest < ActionView::TestCase
+ test "render template" do
+ controller.controller_path = "test"
+ render(:template => "test/calling_partial_with_layout")
+ assert_template "partial_for_use_in_layout"
+ end
+ end
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index b0a4c2a9cc..d22b9fe406 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -19,6 +19,10 @@ class TextHelperTest < ActionView::TestCase
assert_equal 'foobar', output_buffer
end
+ def test_simple_format_should_be_html_safe
+ assert simple_format("<b> test with html tags </b>").html_safe?
+ end
+
def test_simple_format
assert_equal "<p></p>", simple_format(nil)
@@ -36,43 +40,25 @@ class TextHelperTest < ActionView::TestCase
assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test')
end
- def test_simple_format_should_be_html_safe
- assert simple_format("<b> test with html tags </b>").html_safe?
+ def test_simple_format_should_sanitize_input_when_sanitize_option_is_not_false
+ assert_equal "<p><b> test with unsafe string </b></p>", simple_format("<b> test with unsafe string </b><script>code!</script>")
end
- def test_simple_format_should_escape_unsafe_input
- assert_equal "<p>&lt;b&gt; test with unsafe string &lt;/b&gt;&lt;script&gt;code!&lt;/script&gt;</p>", simple_format("<b> test with unsafe string </b><script>code!</script>")
+ def test_simple_format_should_not_sanitize_input_when_sanitize_option_is_false
+ assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false)
end
- def test_simple_format_should_not_escape_input_if_safe_option
- assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :safe => true)
+ def test_truncate_should_not_be_html_safe
+ assert !truncate("Hello World!", :length => 12).html_safe?
end
- def test_simple_format_should_not_escape_safe_input
- assert_equal "<p><b> test with safe string </b></p>", simple_format("<b> test with safe string </b>".html_safe)
- end
-
- def test_truncate_should_be_html_safe
- assert truncate("Hello World!", :length => 12).html_safe?
- end
-
def test_truncate
assert_equal "Hello World!", truncate("Hello World!", :length => 12)
assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12)
end
- def test_truncate_should_escape_unsafe_input
- assert_equal "Hello &lt...", truncate("Hello <script>code!</script>World!!", :length => 12)
- end
-
- def test_truncate_should_not_escape_input_if_safe_option
- assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!", :length => 12, :safe => true)
- assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12, :safe => true)
- end
-
- def test_truncate_should_not_escape_safe_input
- assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!".html_safe, :length => 12)
- assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!".html_safe, :length => 12)
+ def test_truncate_should_not_escape_input
+ assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12)
end
def test_truncate_should_use_default_length_of_30
@@ -138,24 +124,17 @@ class TextHelperTest < ActionView::TestCase
assert_equal ' ', highlight(' ', 'blank text is returned verbatim')
end
- def test_highlight_should_escape_unsafe_input
+ def test_highlight_should_sanitize_input
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning&lt;script&gt;code!&lt;/script&gt;",
+ "This is a <strong class=\"highlight\">beautiful</strong> morning",
highlight("This is a beautiful morning<script>code!</script>", "beautiful")
)
end
- def test_highlight_should_not_escape_input_if_safe_option
- assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>",
- highlight("This is a beautiful morning<script>code!</script>", "beautiful", :safe => true)
- )
- end
-
- def test_highlight_should_not_escape_safe_input
+ def test_highlight_should_not_sanitize_if_sanitize_option_if_false
assert_equal(
"This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>",
- highlight("This is a beautiful morning<script>code!</script>".html_safe, "beautiful")
+ highlight("This is a beautiful morning<script>code!</script>", "beautiful", :sanitize => false)
)
end
@@ -189,23 +168,23 @@ class TextHelperTest < ActionView::TestCase
def test_highlight_with_html
assert_equal(
- "&lt;p&gt;This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day&lt;/p&gt;",
+ "<p>This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
highlight("<p>This is a beautiful morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "&lt;p&gt;This is a &lt;em&gt;<strong class=\"highlight\">beautiful</strong>&lt;/em&gt; morning, but also a <strong class=\"highlight\">beautiful</strong> day&lt;/p&gt;",
+ "<p>This is a <em><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
highlight("<p>This is a <em>beautiful</em> morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "&lt;p&gt;This is a &lt;em class=&quot;error&quot;&gt;<strong class=\"highlight\">beautiful</strong>&lt;/em&gt; morning, but also a <strong class=\"highlight\">beautiful</strong> &lt;span class=&quot;last&quot;&gt;day&lt;/span&gt;&lt;/p&gt;",
+ "<p>This is a <em class=\"error\"><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> <span class=\"last\">day</span></p>",
highlight("<p>This is a <em class=\"error\">beautiful</em> morning, but also a beautiful <span class=\"last\">day</span></p>", "beautiful")
)
assert_equal(
- "&lt;p class=&quot;<strong class=\"highlight\">beautiful</strong>&quot;&gt;This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day&lt;/p&gt;",
+ "<p class=\"beautiful\">This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
highlight("<p class=\"beautiful\">This is a beautiful morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "&lt;p&gt;This is a <strong class=\"highlight\">beautiful</strong> &lt;a href=&quot;http://example.com/<strong class=\"highlight\">beautiful</strong>#top?what=<strong class=\"highlight\">beautiful</strong>%20morning&amp;when=now+then&quot;&gt;morning&lt;/a&gt;, but also a <strong class=\"highlight\">beautiful</strong> day&lt;/p&gt;",
+ "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful#top?what=beautiful%20morning&amp;when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful")
)
end
@@ -217,6 +196,10 @@ class TextHelperTest < ActionView::TestCase
assert_nil excerpt("This is a beautiful morning", "day")
end
+ def test_excerpt_should_not_be_html_safe
+ assert !excerpt('This is a beautiful! morning', 'beautiful', 5).html_safe?
+ end
+
def test_excerpt_in_borderline_cases
assert_equal("", excerpt("", "", 0))
assert_equal("a", excerpt("a", "a", 0))
@@ -323,9 +306,13 @@ class TextHelperTest < ActionView::TestCase
end
end
- def generate_result(link_text, href = nil)
+ def generate_result(link_text, href = nil, escape = false)
href ||= link_text
- %{<a href="#{CGI::escapeHTML href}">#{CGI::escapeHTML link_text}</a>}
+ if escape
+ %{<a href="#{CGI::escapeHTML href}">#{CGI::escapeHTML link_text}</a>}
+ else
+ %{<a href="#{href}">#{link_text}</a>}
+ end
end
def test_auto_link_should_be_html_safe
@@ -430,19 +417,14 @@ class TextHelperTest < ActionView::TestCase
assert_equal %(<p>#{link10_result} Link</p>), auto_link("<p>#{link10_raw} Link</p>")
end
- def test_auto_link_should_sanitize_unsafe_input
- link_raw = %{http://www.rubyonrails.com?id=1&num=2}
- assert_equal %{<a href="http://www.rubyonrails.com?id=1&amp;num=2">http://www.rubyonrails.com?id=1&amp;num=2</a>}, auto_link(link_raw)
- end
-
- def test_auto_link_should_sanitize_unsafe_input
+ def test_auto_link_should_sanitize_input_when_sanitize_option_is_not_false
link_raw = %{http://www.rubyonrails.com?id=1&num=2}
- assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw, :safe => true)
+ assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw)
end
- def test_auto_link_should_not_sanitize_safe_input
+ def test_auto_link_should_not_sanitize_input_when_sanitize_option_is_false
link_raw = %{http://www.rubyonrails.com?id=1&num=2}
- assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw.html_safe)
+ assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw, :sanitize => false)
end
def test_auto_link_other_protocols
@@ -453,6 +435,7 @@ class TextHelperTest < ActionView::TestCase
z39_scheme = 'z39.50r://host:696/db'
chrome_scheme = 'chrome://package/section/path'
view_source = 'view-source:http://en.wikipedia.org/wiki/URI_scheme'
+ assert_equal generate_result(file_scheme), auto_link(file_scheme)
assert_equal generate_result(z39_scheme), auto_link(z39_scheme)
assert_equal generate_result(chrome_scheme), auto_link(chrome_scheme)
assert_equal generate_result(view_source), auto_link(view_source)
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index d4a00c5073..1dba664539 100755..100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -1,5 +1,8 @@
dir = File.dirname(__FILE__)
+gem 'rdoc', '= 2.2'
+require 'rdoc'
+
require 'rake/testtask'
task :default => :test
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 36d89c2492..5779ba3b29 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/keys'
require 'active_model/errors'
+require 'active_model/validations/callbacks'
module ActiveModel
@@ -164,8 +165,7 @@ module ActiveModel
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
- _run_validate_callbacks
- errors.empty?
+ run_validations!
ensure
self.validation_context = current_context
end
@@ -194,6 +194,13 @@ module ActiveModel
# end
#
alias :read_attribute_for_validation :send
+
+ protected
+
+ def run_validations!
+ _run_validate_callbacks
+ errors.empty?
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
new file mode 100644
index 0000000000..afd65d3dd5
--- /dev/null
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -0,0 +1,57 @@
+require 'active_support/callbacks'
+
+module ActiveModel
+ module Validations
+ module Callbacks
+ # == Active Model Validation callbacks
+ #
+ # Provides an interface for any class to have <tt>before_validation</tt> and
+ # <tt>after_validation</tt> callbacks.
+ #
+ # First, extend ActiveModel::Callbacks from the class you are creating:
+ #
+ # class MyModel
+ # include ActiveModel::Validations::Callbacks
+ #
+ # before_validation :do_stuff_before_validation
+ # after_validation :do_tuff_after_validation
+ # end
+ #
+ # Like other before_* callbacks if <tt>before_validation</tt> returns false
+ # then <tt>valid?</tt> will not be called.
+ extend ActiveSupport::Concern
+
+ included do
+ include ActiveSupport::Callbacks
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ end
+
+ module ClassMethods
+ def before_validation(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array.wrap(options[:if])
+ options[:if] << "self.validation_context == :#{options[:on]}"
+ end
+ set_callback(:validation, :before, *args, &block)
+ end
+
+ def after_validation(*args, &block)
+ options = args.extract_options!
+ options[:prepend] = true
+ options[:if] = Array.wrap(options[:if])
+ options[:if] << "!halted && value != false"
+ options[:if] << "self.validation_context == :#{options[:on]}" if options[:on]
+ set_callback(:validation, :after, *(args << options), &block)
+ end
+ end
+
+ protected
+
+ # Overwrite run validations to include callbacks.
+ def run_validations!
+ _run_validation_callbacks { super }
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
new file mode 100644
index 0000000000..67b21eb106
--- /dev/null
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -0,0 +1,77 @@
+# encoding: utf-8
+require 'cases/helper'
+
+class Dog
+ include ActiveModel::Validations
+ include ActiveModel::Validations::Callbacks
+
+ attr_accessor :name, :history
+
+ def history
+ @history ||= []
+ end
+end
+
+class DogWithMethodCallbacks < Dog
+ before_validation :set_before_validation_marker
+ after_validation :set_after_validation_marker
+
+ def set_before_validation_marker; self.history << 'before_validation_marker'; end
+ def set_after_validation_marker; self.history << 'after_validation_marker' ; end
+end
+
+class DogValidtorsAreProc < Dog
+ before_validation { self.history << 'before_validation_marker' }
+ after_validation { self.history << 'after_validation_marker' }
+end
+
+class DogWithTwoValidators < Dog
+ before_validation { self.history << 'before_validation_marker1' }
+ before_validation { self.history << 'before_validation_marker2' }
+end
+
+class DogValidatorReturningFalse < Dog
+ before_validation { false }
+ before_validation { self.history << 'before_validation_marker2' }
+end
+
+class DogWithMissingName < Dog
+ before_validation { self.history << 'before_validation_marker' }
+ validates_presence_of :name
+end
+
+class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called
+ d = DogWithMethodCallbacks.new
+ d.valid?
+ assert_equal ['before_validation_marker', 'after_validation_marker'], d.history
+ end
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called_with_proc
+ d = DogValidtorsAreProc.new
+ d.valid?
+ assert_equal ['before_validation_marker', 'after_validation_marker'], d.history
+ end
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called_in_declared_order
+ d = DogWithTwoValidators.new
+ d.valid?
+ assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history
+ end
+
+ def test_further_callbacks_should_not_be_called_if_before_validation_returns_false
+ d = DogValidatorReturningFalse.new
+ output = d.valid?
+ assert_equal [], d.history
+ assert_equal false, output
+ end
+
+ def test_validation_test_should_be_done
+ d = DogWithMissingName.new
+ output = d.valid?
+ assert_equal ['before_validation_marker'], d.history
+ assert_equal false, output
+ end
+
+end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 7d5e550a7c..def519c05d 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,3 +1,8 @@
+*Rails 3.0.0 [RC1] (unreleased)*
+
+* PostgreSQL: ensure the database time zone matches Ruby's time zone. #4895 [Aaron Patterson]
+
+
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* Fixed that ActiveRecord::Base.compute_type would swallow NoMethodError #4751 [Andrew Bloomgarden, Andrew White]
diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS
index 8559d72647..03e561a50b 100644
--- a/activerecord/RUNNING_UNIT_TESTS
+++ b/activerecord/RUNNING_UNIT_TESTS
@@ -32,5 +32,7 @@ being initialized - you can initialize the schema with:
rake test_mysql TEST=test/cases/aaa_create_tables_test.rb
+The incantation for running a particular test looks like this
+ ruby -w -I"lib:test:test/connections/native_postgresql" test/cases/datatype_test_postgresql.rb -n test_timestamp_with_zone_values_without_rails_time_zone_support
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index bf05389eae..12e094b406 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,4 +1,5 @@
-require 'rubygems'
+gem 'rdoc', '= 2.2'
+require 'rdoc'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
@@ -222,5 +223,5 @@ end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
+ Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/ar", "doc").upload
end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index f69576b240..f69576b240 100755..100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 185e298f5c..399c1bf58a 100755..100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -2066,7 +2066,7 @@ module ActiveRecord
unless klass.descends_from_active_record?
sti_column = aliased_table[klass.inheritance_column]
sti_condition = sti_column.eq(klass.sti_name)
- klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
+ klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
@join << sti_condition
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 5b5094bcab..f8d46bcb48 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -390,7 +390,11 @@ module ActiveRecord
begin
if !loaded?
if @target.is_a?(Array) && @target.any?
- @target = find_target + @target.find_all {|t| t.new_record? }
+ @target = find_target.map do |f|
+ i = @target.index(f)
+ t = @target.delete_at(i) if i
+ (t && t.changed?) ? t : f
+ end + @target
else
@target = find_target
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 93249fc96c..3f81ca7555 100755..100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -2,6 +2,7 @@ require 'yaml'
require 'set'
require 'active_support/benchmarkable'
require 'active_support/dependencies'
+require 'active_support/descendants_tracker'
require 'active_support/time'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
@@ -276,28 +277,6 @@ module ActiveRecord #:nodoc:
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false
- def self.inherited(child) #:nodoc:
- @@subclasses[self] ||= []
- @@subclasses[self] << child
- super
- end
-
- def self.reset_subclasses #:nodoc:
- nonreloadables = []
- subclasses.each do |klass|
- unless ActiveSupport::Dependencies.autoloaded? klass
- nonreloadables << klass
- next
- end
- klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
- klass.instance_methods(false).each { |m| klass.send :undef_method, m }
- end
- @@subclasses = {}
- nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
- end
-
- @@subclasses = {}
-
##
# :singleton-method:
# Contains the database configuration - as is typically stored in config/database.yml -
@@ -812,7 +791,7 @@ module ActiveRecord #:nodoc:
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
- subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
+ descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
end
def attribute_method?(attribute)
@@ -977,7 +956,7 @@ module ActiveRecord #:nodoc:
def type_condition
sti_column = arel_table[inheritance_column]
condition = sti_column.eq(sti_name)
- subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
+ descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
condition
end
@@ -1167,14 +1146,6 @@ module ActiveRecord #:nodoc:
with_scope(method_scoping, :overwrite, &block)
end
- # Returns a list of all subclasses of this class, meaning all descendants.
- def subclasses
- @@subclasses[self] ||= []
- @@subclasses[self] + @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
- end
-
- public :subclasses
-
# Sets the default options for the model. The format of the
# <tt>options</tt> argument is the same as in find.
#
@@ -1902,6 +1873,7 @@ module ActiveRecord #:nodoc:
extend ActiveModel::Naming
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
include ActiveModel::Conversion
include Validations
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 7b7de0b070..637dac450b 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -236,8 +236,7 @@ module ActiveRecord
included do
extend ActiveModel::Callbacks
-
- define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ include ActiveModel::Validations::Callbacks
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
@@ -251,29 +250,6 @@ module ActiveRecord
send(meth.to_sym, meth.to_sym)
end
end
-
- def before_validation(*args, &block)
- options = args.last
- if options.is_a?(Hash) && options[:on]
- options[:if] = Array.wrap(options[:if])
- options[:if] << "@_on_validate == :#{options[:on]}"
- end
- set_callback(:validation, :before, *args, &block)
- end
-
- def after_validation(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- options[:if] = Array.wrap(options[:if])
- options[:if] << "!halted && value != false"
- options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
- set_callback(:validation, :after, *(args << options), &block)
- end
- end
-
- def valid?(*) #:nodoc:
- @_on_validate = new_record? ? :create : :update
- _run_validation_callbacks { super }
end
def destroy #:nodoc:
@@ -288,6 +264,7 @@ module ActiveRecord
end
private
+
def create_or_update #:nodoc:
_run_save_callbacks { super }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index b9fb452eee..25432e9985 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -304,7 +304,7 @@ module ActiveRecord
begin
record.rolledback!(rollback)
rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
+ record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
end
@@ -319,7 +319,7 @@ module ActiveRecord
begin
record.committed!
rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
+ record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4ee9fee4a9..4ee9fee4a9 100755..100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index bb8850f134..e84242601b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -927,7 +927,12 @@ module ActiveRecord
# If using Active Record's time zone support configure the connection to return
# TIMESTAMP WITH ZONE types in UTC.
- execute("SET time zone 'UTC'") if ActiveRecord::Base.default_timezone == :utc
+ if ActiveRecord::Base.default_timezone == :utc
+ execute("SET time zone 'UTC'")
+ else
+ offset = Time.local(2000).utc_offset / 3600
+ execute("SET time zone '#{offset}'")
+ end
end
# Returns the current ID of a table's sequence.
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 9554dd8826..5f80bd86df 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -107,8 +107,9 @@ module ActiveRecord
end
protected
+
def observed_subclasses
- observed_classes.sum([]) { |klass| klass.send(:subclasses) }
+ observed_classes.sum([]) { |klass| klass.send(:descendants) }
end
def observe_callbacks?
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 37f1ec11a6..36df878e1b 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -69,7 +69,6 @@ module ActiveRecord
unless app.config.cache_classes
ActiveSupport.on_load(:active_record) do
ActionDispatch::Callbacks.after do
- ActiveRecord::Base.reset_subclasses
ActiveRecord::Base.clear_reloadable_connections!
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 7a48a6596a..50e94134f5 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -116,45 +116,7 @@ module ActiveRecord
def build_arel
arel = table
- joined_associations = []
- association_joins = []
-
- joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
-
- joins.each do |join|
- association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
- end
-
- stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
-
- non_association_joins = (joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
- custom_joins = custom_join_sql(*non_association_joins)
-
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
-
- join_dependency.graft(*stashed_association_joins)
-
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
-
- to_join = []
-
- join_dependency.join_associations.each do |association|
- if (association_relation = association.relation).is_a?(Array)
- to_join << [association_relation.first, association.join_class, association.association_join.first]
- to_join << [association_relation.last, association.join_class, association.association_join.last]
- else
- to_join << [association_relation, association.join_class, association.association_join]
- end
- end
-
- to_join.each do |tj|
- unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
- joined_associations << tj
- arel = arel.join(tj[0], tj[1]).on(*tj[2])
- end
- end
-
- arel = arel.join(custom_joins)
+ arel = build_joins(arel, @joins_values) if @joins_values.present?
@where_values.uniq.each do |where|
next if where.blank?
@@ -168,9 +130,7 @@ module ActiveRecord
end
end
- @having_values.uniq.each do |h|
- arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
- end
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?})
arel = arel.take(@limit_value) if @limit_value.present?
arel = arel.skip(@offset_value) if @offset_value.present?
@@ -181,18 +141,16 @@ module ActiveRecord
selects = @select_values.uniq
- quoted_table_name = @klass.quoted_table_name
-
if selects.present?
selects.each do |s|
@implicit_readonly = false
arel = arel.project(s) if s.present?
end
else
- arel = arel.project(quoted_table_name + '.*')
+ arel = arel.project(@klass.quoted_table_name + '.*')
end
- arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name)
+ arel = @from_value.present? ? arel.from(@from_value) : arel.from(@klass.quoted_table_name)
case @lock_value
when TrueClass
@@ -221,6 +179,48 @@ module ActiveRecord
private
+ def build_joins(relation, joins)
+ joined_associations = []
+ association_joins = []
+
+ joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
+
+ joins.each do |join|
+ association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
+ end
+
+ stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
+
+ non_association_joins = (joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
+ custom_joins = custom_join_sql(*non_association_joins)
+
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
+
+ join_dependency.graft(*stashed_association_joins)
+
+ @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
+
+ to_join = []
+
+ join_dependency.join_associations.each do |association|
+ if (association_relation = association.relation).is_a?(Array)
+ to_join << [association_relation.first, association.join_class, association.association_join.first]
+ to_join << [association_relation.last, association.join_class, association.association_join.last]
+ else
+ to_join << [association_relation, association.join_class, association.association_join]
+ end
+ end
+
+ to_join.each do |tj|
+ unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
+ joined_associations << tj
+ relation = relation.join(tj[0], tj[1]).on(*tj[2])
+ end
+ end
+
+ relation.join(custom_joins)
+ end
+
def apply_modules(modules)
values = Array.wrap(modules)
@extensions += values if values.present?
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index b4b146994d..a7709b9489 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -322,6 +322,7 @@ module ActiveRecord
if @_start_transaction_state[:level] < 1
restore_state = remove_instance_variable(:@_start_transaction_state)
if restore_state
+ @attributes = @attributes.dup if @attributes.frozen?
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
if restore_state[:id]
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 6b511e83db..b98fd353aa 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -52,12 +52,12 @@ module ActiveRecord
# Runs all the specified validations and returns true if no errors were added otherwise false.
def valid?(context = nil)
context ||= (new_record? ? :create : :update)
- super(context)
+ output = super(context)
deprecated_callback_method(:validate)
deprecated_callback_method(:"validate_on_#{context}")
- errors.empty?
+ errors.empty? && output
end
protected
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index bbb7c53d86..d6ab3257a0 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -1,11 +1,15 @@
class <%= migration_class_name %> < ActiveRecord::Migration
def self.up<% attributes.each do |attribute| %>
- <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end -%>
- <%- end %>
+ <%- if migration_action -%>
+ <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %>
+ <%- end -%>
+ <%- end -%>
end
def self.down<% attributes.reverse.each do |attribute| %>
- <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end -%>
- <%- end %>
+ <%- if migration_action -%>
+ <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %>
+ <%- end -%>
+ <%- end -%>
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index fc08c2178a..646aa88d80 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -65,15 +65,14 @@ class AdapterTest < ActiveRecord::TestCase
end
def test_not_specifying_database_name_for_cross_database_selects
- assert_nothing_raised do
- ActiveRecord::Base.establish_connection({
- :adapter => 'mysql',
- :username => 'rails'
- })
- ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
+ begin
+ assert_nothing_raised do
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database))
+ ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
+ end
+ ensure
+ ActiveRecord::Base.establish_connection 'arunit'
end
-
- ActiveRecord::Base.establish_connection 'arunit'
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 4e4f9c385c..3b89c12a3f 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -712,7 +712,6 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
assert_raise(RuntimeError) { assert !@pirate.save }
- assert before.first.frozen? # the first child was indeed destroyed
assert_equal before, @pirate.reload.send(association_name)
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5c175de6d4..7c74d87b61 100755..100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -2076,10 +2076,6 @@ class BasicsTest < ActiveRecord::TestCase
assert !SubStiPost.descends_from_active_record?
end
- def test_base_subclasses_is_public_method
- assert ActiveRecord::Base.public_methods.map(&:to_sym).include?(:subclasses)
- end
-
def test_find_on_abstract_base_class_doesnt_use_type_condition
old_class = LooseDescendant
Object.send :remove_const, :LooseDescendant
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 377de168b9..377de168b9 100755..100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index ddadde8dcf..6fe3b01281 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1032,7 +1032,7 @@ if ActiveRecord::Base.connection.supports_migrations?
elsif current_adapter?(:SQLiteAdapter)
# - SQLite3 stores a float, in violation of SQL
assert_kind_of BigDecimal, b.value_of_e
- assert_equal BigDecimal("2.71828182845905"), b.value_of_e
+ assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001
else
# - SQL standard is an integer
assert_kind_of Fixnum, b.value_of_e
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 685b11cb03..65d6080ea5 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -466,6 +466,27 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal 'Grace OMalley', @child_1.reload.name
end
+ def test_should_not_overwrite_unsaved_updates_when_loading_association
+ @pirate.reload
+ @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
+ assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name
+ end
+
+ def test_should_preserve_order_when_not_overwriting_unsaved_updates
+ @pirate.reload
+ @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
+ assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id
+ end
+
+ def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates
+ @pirate.reload
+ record = @pirate.class.reflect_on_association(@association_name).klass.new(:name => 'Grace OMalley')
+ @pirate.send(@association_name) << record
+ record.save!
+ @pirate.send(@association_name).last.update_attributes!(:name => 'Polly')
+ assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
+ 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/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index ebc16653cb..df123c9de8 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -221,20 +221,28 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal 2, @first.rollbacks
end
- def test_after_transaction_callbacks_should_not_raise_errors
+ def test_after_transaction_callbacks_should_prevent_callbacks_from_being_called
def @first.last_after_transaction_error=(e); @last_transaction_error = e; end
def @first.last_after_transaction_error; @last_transaction_error; end
@first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";}
@first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";}
+ @second.after_commit_block{|r| r.history << :after_commit}
+ @second.after_rollback_block{|r| r.history << :after_rollback}
- @first.save!
+ Topic.transaction do
+ @first.save!
+ @second.save!
+ end
assert_equal :commit, @first.last_after_transaction_error
+ assert_equal [:after_commit], @second.history
+ @second.history.clear
Topic.transaction do
@first.save!
+ @second.save!
raise ActiveRecord::Rollback
end
-
assert_equal :rollback, @first.last_after_transaction_error
+ assert_equal [:after_rollback], @second.history
end
end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index f1dbe32c6e..d89c8cf381 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -1,7 +1,7 @@
class Pirate < ActiveRecord::Base
belongs_to :parrot, :validate => true
belongs_to :non_validated_parrot, :class_name => 'Parrot'
- has_and_belongs_to_many :parrots, :validate => true
+ has_and_belongs_to_many :parrots, :validate => true, :order => 'parrots.id ASC'
has_and_belongs_to_many :non_validated_parrots, :class_name => 'Parrot'
has_and_belongs_to_many :parrots_with_method_callbacks, :class_name => "Parrot",
:before_add => :log_before_add,
@@ -21,7 +21,7 @@ class Pirate < ActiveRecord::Base
has_one :ship
has_one :update_only_ship, :class_name => 'Ship'
has_one :non_validated_ship, :class_name => 'Ship'
- has_many :birds
+ has_many :birds, :order => 'birds.id ASC'
has_many :birds_with_method_callbacks, :class_name => "Bird",
:before_add => :log_before_add,
:after_add => :log_after_add,
diff --git a/activeresource/Rakefile b/activeresource/Rakefile
index 175b379699..04b08ed8cb 100644
--- a/activeresource/Rakefile
+++ b/activeresource/Rakefile
@@ -1,4 +1,5 @@
-require 'rubygems'
+gem 'rdoc', '= 2.2'
+require 'rdoc'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
@@ -83,5 +84,5 @@ end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
+ Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/ar", "doc").upload
end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 13ec3c3775..abd0664118 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,9 @@
*Rails 3.0.0 [Release Candidate] (unreleased)*
+* Added ActiveSupport::FileUpdateChecker to execute a block only if a set of files changed, used by Router and I18n locale files. [José Valim]
+
+* Added ActiveSupport::DescendantsTracker to track descendants with support to constants reloading. [José Valim]
+
* ActiveSupport::OrderedHash#merge and #merge! accept a block. #4838 [Paul Mucur, fxn]
* Date#since, #ago, #beginning_of_day, #end_of_day, and #xmlschema honor now the user time zone if set. [Geoff Buesing]
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 43f4722dbc..2aebe05de2 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -1,3 +1,5 @@
+gem 'rdoc', '= 2.2'
+require 'rdoc'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/gempackagetask'
@@ -47,5 +49,5 @@ end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/as", "doc").upload
+ Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/as", "doc").upload
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 51edb59c77..51edb59c77 100755..100644
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index e34e46b4cf..3ce5476bbd 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -39,6 +39,9 @@ require "active_support/dependencies/autoload"
module ActiveSupport
extend ActiveSupport::Autoload
+ autoload :DescendantsTracker
+ autoload :FileUpdateChecker
+
# TODO: Narrow this list down
eager_autoload do
autoload :BacktraceCleaner
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 3ff33eea72..c4e1eb2c04 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,6 +1,6 @@
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/inheritable_attributes'
-require 'active_support/core_ext/class/subclasses'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
@@ -85,6 +85,10 @@ module ActiveSupport
module Callbacks
extend Concern
+ included do
+ extend ActiveSupport::DescendantsTracker
+ end
+
def run_callbacks(kind, *args, &block)
send("_run_#{kind}_callbacks", *args, &block)
end
@@ -428,7 +432,7 @@ module ActiveSupport
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
- ([self] + self.descendents).each do |target|
+ ([self] + self.descendants).each do |target|
chain = target.send("_#{name}_callbacks")
yield chain, type, filters, options
target.__define_runner(name)
@@ -502,7 +506,7 @@ module ActiveSupport
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
- self.descendents.each do |target|
+ self.descendants.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 bbd8f5aef6..7d58a8b56a 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -11,9 +11,9 @@ class Class #:nodoc:
# Rubinius
if defined?(Class.__subclasses__)
- def descendents
+ def descendants
subclasses = []
- __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendents }
+ __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendants }
subclasses
end
else
@@ -21,7 +21,7 @@ class Class #:nodoc:
begin
ObjectSpace.each_object(Class.new) {}
- def descendents
+ def descendants
subclasses = []
ObjectSpace.each_object(class << self; self; end) do |k|
subclasses << k unless k == self
@@ -30,7 +30,7 @@ class Class #:nodoc:
end
# JRuby
rescue StandardError
- def descendents
+ def descendants
subclasses = []
ObjectSpace.each_object(Class) do |k|
subclasses << k if k < self
@@ -48,7 +48,7 @@ class Class #:nodoc:
def self.subclasses_of(*superclasses) #:nodoc:
subclasses = []
superclasses.each do |klass|
- subclasses.concat klass.descendents.select {|k| k.anonymous? || k.reachable?}
+ subclasses.concat klass.descendants.select {|k| k.anonymous? || k.reachable?}
end
subclasses
end
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 98565e6750..6002d4ad2a 100644
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb
@@ -12,6 +12,8 @@ class DateTime
#
# DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
+ return self unless zone
+
ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone))
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index 06f077e920..06f077e920 100755..100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index adc9fe3824..a02402aa3f 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -73,6 +73,8 @@ class Time
#
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
+ return self unless zone
+
ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone))
end
end
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
new file mode 100644
index 0000000000..a587d7770c
--- /dev/null
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -0,0 +1,39 @@
+require 'active_support/dependencies'
+
+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] = [] }
+
+ def self.descendants
+ @@descendants
+ end
+
+ def self.clear
+ @@descendants.each do |klass, descendants|
+ if ActiveSupport::Dependencies.autoloaded?(klass)
+ @@descendants.delete(klass)
+ else
+ descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ end
+ end
+ end
+
+ def inherited(base)
+ self.direct_descendants << base
+ super
+ end
+
+ def direct_descendants
+ @@descendants[self]
+ end
+
+ def descendants
+ @@descendants[self].inject([]) do |descendants, klass|
+ descendants << klass
+ descendants.concat klass.descendants
+ end
+ 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
new file mode 100644
index 0000000000..c0b5ca4deb
--- /dev/null
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -0,0 +1,37 @@
+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.
+ #
+ # routes_reloader = ActiveSupport::FileUpdateChecker.new(paths) do
+ # paths.each { |p| load(p) }
+ # Rails::Application.routes.reload!
+ # end
+ #
+ # ActionDispatch::Callbacks.to_prepare do
+ # routes_reloader.execute_if_updated
+ # end
+ #
+ class FileUpdateChecker
+ attr_reader :paths, :last_update_at
+
+ def initialize(paths, calculate=false, &block)
+ @paths = paths
+ @block = block
+ @last_update_at = updated_at if calculate
+ end
+
+ def updated_at
+ paths.map { |path| File.stat(path).mtime }.max
+ end
+
+ def execute_if_updated
+ current_update_at = self.updated_at
+ if @last_update_at != current_update_at
+ @last_update_at = current_update_at
+ @block.call
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index 0ffdd904fd..45b9d20c01 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -4,5 +4,6 @@ rescue LoadError => e
$stderr.puts "You don't have i18n installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
+
I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
ActiveSupport.run_load_hooks(:i18n)
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
new file mode 100644
index 0000000000..d82e54f1d4
--- /dev/null
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -0,0 +1,80 @@
+require "active_support"
+require "rails"
+require "active_support/file_update_checker"
+
+module I18n
+ class Railtie < Rails::Railtie
+ config.i18n = ActiveSupport::OrderedOptions.new
+ config.i18n.railties_load_path = []
+ config.i18n.load_path = []
+ config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
+
+ def self.reloader
+ @reloader ||= ActiveSupport::FileUpdateChecker.new([]){ I18n.reload! }
+ end
+
+ # Add I18n::Railtie.reloader to ActionDispatch callbacks. Since, at this
+ # point, no path was added to the reloader, I18n.reload! is not triggered
+ # on to_prepare callbacks. This will only happen on the config.after_initialize
+ # callback below.
+ initializer "i18n.callbacks" do
+ ActionDispatch::Callbacks.to_prepare do
+ I18n::Railtie.reloader.execute_if_updated
+ end
+ end
+
+ # Set the i18n configuration only after initialization since a lot of
+ # configuration is still usually done in application initializers.
+ config.after_initialize do |app|
+ fallbacks = app.config.i18n.delete(:fallbacks)
+
+ app.config.i18n.each do |setting, value|
+ case setting
+ when :railties_load_path
+ app.config.i18n.load_path.unshift(*value)
+ when :load_path
+ I18n.load_path += value
+ else
+ I18n.send("#{setting}=", value)
+ end
+ end
+
+ init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
+
+ reloader.paths.concat I18n.load_path
+ reloader.execute_if_updated
+ end
+
+ protected
+
+ def self.include_fallbacks_module
+ I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ end
+
+ def self.init_fallbacks(fallbacks)
+ include_fallbacks_module
+
+ args = case fallbacks
+ when ActiveSupport::OrderedOptions
+ [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
+ when Hash, Array
+ Array.wrap(fallbacks)
+ else # TrueClass
+ []
+ end
+
+ I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
+ end
+
+ def self.validate_fallbacks(fallbacks)
+ case fallbacks
+ when ActiveSupport::OrderedOptions
+ !fallbacks.empty?
+ when TrueClass, Array, Hash
+ true
+ else
+ raise "Unexpected fallback type #{fallbacks.inspect}"
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/json/backends/yajl.rb b/activesupport/lib/active_support/json/backends/yajl.rb
index d76f8b03e4..64e50e0d87 100644
--- a/activesupport/lib/active_support/json/backends/yajl.rb
+++ b/activesupport/lib/active_support/json/backends/yajl.rb
@@ -1,4 +1,4 @@
-require 'yajl-ruby' unless defined?(Yajl)
+require 'yajl' unless defined?(Yajl)
module ActiveSupport
module JSON
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index d6ccb4bac1..04193bfa65 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -50,10 +50,6 @@ module ActiveSupport #:nodoc:
end
end
- def <=>(other)
- @wrapped_string <=> other
- end
-
# Forward all undefined methods to the wrapped string.
def method_missing(method, *args, &block)
if method.to_s =~ /!$/
@@ -87,6 +83,16 @@ module ActiveSupport #:nodoc:
include Comparable
+ # Returns <tt>-1</tt>, <tt>0</tt> or <tt>+1</tt> depending on whether the Chars object is to be sorted before,
+ # equal or after the object on the right side of the operation. It accepts any object that implements +to_s+.
+ # See <tt>String#<=></tt> for more details.
+ #
+ # Example:
+ # 'é'.mb_chars <=> 'ü'.mb_chars #=> -1
+ def <=>(other)
+ @wrapped_string <=> other.to_s
+ end
+
if RUBY_VERSION < "1.9"
# Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
# +false+ otherwise.
@@ -94,16 +100,6 @@ module ActiveSupport #:nodoc:
$KCODE == 'UTF8' && consumes?(string)
end
- # Returns <tt>-1</tt>, <tt>0</tt> or <tt>+1</tt> depending on whether the Chars object is to be sorted before,
- # equal or after the object on the right side of the operation. It accepts any object that implements +to_s+.
- # See <tt>String#<=></tt> for more details.
- #
- # Example:
- # 'é'.mb_chars <=> 'ü'.mb_chars #=> -1
- def <=>(other)
- @wrapped_string <=> other.to_s
- end
-
# Returns a new Chars object containing the _other_ object concatenated to the string.
#
# Example:
@@ -375,6 +371,16 @@ module ActiveSupport #:nodoc:
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
end
+ # Capitalizes the first letter of every word, when possible.
+ #
+ # Example:
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
+ # "日本語".mb_chars.titleize # => "日本語"
+ def titleize
+ chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.apply_mapping $1, :uppercase_mapping })
+ end
+ alias_method :titlecase, :titleize
+
# Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
# passing strings to databases and validations.
#
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 59f9ab18b1..1f32f8718f 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -1,5 +1,6 @@
require "active_support"
require "rails"
+require "active_support/i18n_railtie"
module ActiveSupport
class Railtie < Rails::Railtie
@@ -26,75 +27,4 @@ module ActiveSupport
Time.zone_default = zone_default
end
end
-end
-
-module I18n
- class Railtie < Rails::Railtie
- config.i18n = ActiveSupport::OrderedOptions.new
- config.i18n.railties_load_path = []
- config.i18n.load_path = []
- config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
-
- initializer "i18n.initialize" do
- ActiveSupport.on_load(:i18n) do
- I18n.reload!
-
- ActionDispatch::Callbacks.to_prepare do
- I18n.reload!
- end
- end
- end
-
- # Set the i18n configuration from config.i18n but special-case for
- # the load_path which should be appended to what's already set instead of overwritten.
- config.after_initialize do |app|
- fallbacks = app.config.i18n.delete(:fallbacks)
-
- app.config.i18n.each do |setting, value|
- case setting
- when :railties_load_path
- app.config.i18n.load_path.unshift(*value)
- when :load_path
- I18n.load_path += value
- else
- I18n.send("#{setting}=", value)
- end
- end
-
- init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
- I18n.reload!
- end
-
- class << self
- protected
-
- def init_fallbacks(fallbacks)
- include_fallbacks_module
- args = case fallbacks
- when ActiveSupport::OrderedOptions
- [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
- when Hash, Array
- Array.wrap(fallbacks)
- else # TrueClass
- []
- end
- I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
- end
-
- def include_fallbacks_module
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
- end
-
- def validate_fallbacks(fallbacks)
- case fallbacks
- when ActiveSupport::OrderedOptions
- !fallbacks.empty?
- when TrueClass, Array, Hash
- true
- else
- raise "Unexpected fallback type #{fallbacks.inspect}"
- end
- end
- end
- end
end \ No newline at end of file
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 24eea1e40b..cd628a956d 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -260,14 +260,8 @@ begin
end
protected
- if GC.respond_to?(:enable_stats)
- def with_gc_stats
- GC.enable_stats
- yield
- ensure
- GC.disable_stats
- end
- elsif defined?(GC::Profiler)
+ # Ruby 1.9 + extented GC profiler patch
+ if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data)
def with_gc_stats
GC.start
GC.disable
@@ -277,6 +271,16 @@ begin
GC::Profiler.disable
GC.enable
end
+
+ # Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker)
+ elsif GC.respond_to?(:enable_stats)
+ def with_gc_stats
+ GC.enable_stats
+ yield
+ ensure
+ GC.disable_stats
+ end
+
else
def with_gc_stats
yield
@@ -319,7 +323,7 @@ begin
def initialize(*args)
# FIXME: yeah my CPU is 2.33 GHz
- RubyProf.cpu_frequency = 2.33e9
+ RubyProf.cpu_frequency = 2.33e9 unless RubyProf.cpu_frequency > 0
super
end
@@ -331,38 +335,8 @@ begin
class Memory < Base
Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
- # ruby-prof wrapper
- if RubyProf.respond_to?(:measure_memory)
- def measure
- RubyProf.measure_memory / 1024.0
- end
-
- # Ruby 1.8 + railsbench patch
- elsif GC.respond_to?(:allocated_size)
- def measure
- GC.allocated_size / 1024.0
- end
-
- # Ruby 1.8 + lloyd patch
- elsif GC.respond_to?(:heap_info)
- def measure
- GC.heap_info['heap_current_memory'] / 1024.0
- end
-
- # Ruby 1.9 with total_malloc_allocated_size patch
- elsif GC.respond_to?(:malloc_total_allocated_size)
- def measure
- GC.total_malloc_allocated_size / 1024.0
- end
-
- # Ruby 1.9 unpatched
- elsif GC.respond_to?(:malloc_allocated_size)
- def measure
- GC.malloc_allocated_size / 1024.0
- end
-
- # Ruby 1.9 + GC profiler patch
- elsif defined?(GC::Profiler)
+ # Ruby 1.9 + extended GC profiler patch
+ if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data)
def measure
GC.enable
GC.start
@@ -370,6 +344,12 @@ begin
GC.disable
kb
end
+
+ # Ruby 1.8 + ruby-prof wrapper
+ elsif RubyProf.respond_to?(:measure_memory)
+ def measure
+ RubyProf.measure_memory / 1024.0
+ end
end
def format(measurement)
@@ -380,27 +360,21 @@ begin
class Objects < Base
Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
- if RubyProf.respond_to?(:measure_allocations)
- def measure
- RubyProf.measure_allocations
- end
-
- # Ruby 1.8 + railsbench patch
- elsif ObjectSpace.respond_to?(:allocated_objects)
- def measure
- ObjectSpace.allocated_objects
- end
-
- # Ruby 1.9 + GC profiler patch
- elsif defined?(GC::Profiler)
+ # Ruby 1.9 + extented GC profiler patch
+ if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data)
def measure
GC.enable
GC.start
- last = GC::Profiler.data.last
- count = last[:HEAP_LIVE_OBJECTS] + last[:HEAP_FREE_OBJECTS]
+ count = GC::Profiler.data.last[:HEAP_TOTAL_OBJECTS]
GC.disable
count
end
+
+ # Ruby 1.8 + ruby-prof wrapper
+ elsif RubyProf.respond_to?(:measure_allocations)
+ def measure
+ RubyProf.measure_allocations
+ end
end
def format(measurement)
@@ -411,17 +385,20 @@ begin
class GcRuns < Base
Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
- if RubyProf.respond_to?(:measure_gc_runs)
+ # Ruby 1.9 + extented GC profiler patch
+ if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data)
def measure
- RubyProf.measure_gc_runs
- end
- elsif GC.respond_to?(:collections)
- def measure
- GC.collections
+ GC.enable
+ GC.start
+ count = GC::Profiler.data.last[:GC_RUNS]
+ GC.disable
+ count
end
- elsif GC.respond_to?(:heap_info)
+
+ # Ruby 1.8 + ruby-prof wrapper
+ elsif RubyProf.respond_to?(:measure_gc_runs)
def measure
- GC.heap_info['num_gc_passes']
+ RubyProf.measure_gc_runs
end
end
@@ -433,13 +410,20 @@ begin
class GcTime < Base
Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
- if RubyProf.respond_to?(:measure_gc_time)
+ # Ruby 1.9 + extented GC profiler patch
+ if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data)
def measure
- RubyProf.measure_gc_time
+ GC.enable
+ GC.start
+ sec = GC::Profiler.data.inject(0) { |total, run| total += run[:GC_TIME] }
+ GC.disable
+ sec
end
- elsif GC.respond_to?(:time)
+
+ # Ruby 1.8 + ruby-prof wrapper
+ elsif RubyProf.respond_to?(:measure_gc_time)
def measure
- GC.time
+ RubyProf.measure_gc_time
end
end
@@ -452,4 +436,4 @@ begin
end
end
rescue LoadError
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 2cf5bd6ea9..cf11f4d28f 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -737,6 +737,13 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
end
end
+ def test_nil_time_zone
+ Time.use_zone nil do
+ assert !@t.in_time_zone.respond_to?(:period), 'no period method'
+ assert !@dt.in_time_zone.respond_to?(:period), 'no period method'
+ end
+ end
+
def test_in_time_zone_with_argument
Time.use_zone 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone)
assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @t.in_time_zone('Alaska').inspect
diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test.rb
new file mode 100644
index 0000000000..3a424180de
--- /dev/null
+++ b/activesupport/test/descendants_tracker_test.rb
@@ -0,0 +1,75 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support'
+require 'active_support/core_ext/hash/slice'
+
+class DescendantsTrackerTest < Test::Unit::TestCase
+ class Parent
+ extend ActiveSupport::DescendantsTracker
+ end
+
+ class Child1 < Parent
+ end
+
+ class Child2 < Parent
+ end
+
+ class Grandchild1 < Child1
+ end
+
+ class Grandchild2 < Child1
+ end
+
+ ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2]
+
+ def test_descendants
+ assert_equal [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
+ assert_equal [Grandchild1, Grandchild2], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+
+ def test_direct_descendants
+ assert_equal [Child1, Child2], Parent.direct_descendants
+ assert_equal [Grandchild1, Grandchild2], Child1.direct_descendants
+ assert_equal [], Child2.direct_descendants
+ end
+
+ def test_clear_with_autoloaded_parent_children_and_granchildren
+ mark_as_autoloaded *ALL do
+ ActiveSupport::DescendantsTracker.clear
+ assert ActiveSupport::DescendantsTracker.descendants.slice(*ALL).empty?
+ end
+ end
+
+ def test_clear_with_autoloaded_children_and_granchildren
+ mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child2], Parent.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ def test_clear_with_autoloaded_granchildren
+ mark_as_autoloaded Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child1, Child2], Parent.descendants
+ assert_equal [], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ protected
+
+ def mark_as_autoloaded(*klasses)
+ old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
+ ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
+
+ old_descendants = ActiveSupport::DescendantsTracker.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)
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
new file mode 100644
index 0000000000..baf29cc337
--- /dev/null
+++ b/activesupport/test/file_update_checker_test.rb
@@ -0,0 +1,56 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support'
+require 'fileutils'
+
+MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
+
+class FileUpdateCheckerTest < Test::Unit::TestCase
+ FILES = %w(1.txt 2.txt 3.txt)
+
+ def setup
+ FileUtils.touch(FILES)
+ end
+
+ def teardown
+ FileUtils.rm(FILES)
+ end
+
+ def test_should_not_execute_the_block_if_no_paths_are_given
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 }
+ checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_invoke_the_block_on_first_call_if_it_does_not_calculate_last_updated_at_on_load
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
+ checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_not_invoke_the_block_on_first_call_if_it_calculates_last_updated_at_on_load
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 }
+ checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_not_invoke_the_block_if_no_file_has_changed
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
+ 5.times { checker.execute_if_updated }
+ assert_equal 1, i
+ end
+
+ def test_should_invoke_the_block_if_a_file_has_changed
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
+ checker.execute_if_updated
+ sleep(1)
+ FileUtils.touch(FILES)
+ checker.execute_if_updated
+ assert_equal 2, i
+ end
+end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index f7a5834527..602828ef5f 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -443,6 +443,11 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert_equal 'Abc', 'abc'.mb_chars.capitalize
end
+ def test_titleize_should_work_on_ascii_characters
+ assert_equal '', ''.mb_chars.titleize
+ assert_equal 'Abc Abc', 'abc abc'.mb_chars.titleize
+ end
+
def test_respond_to_knows_which_methods_the_proxy_responds_to
assert ''.mb_chars.respond_to?(:slice) # Defined on Chars
assert ''.mb_chars.respond_to?(:capitalize!) # Defined on Chars
@@ -480,6 +485,15 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase
end
end
+ def test_titleize_should_be_unicode_aware
+ assert_equal "Él Que Se Enteró", chars("ÉL QUE SE ENTERÓ").titleize
+ assert_equal "Абвг Абвг", chars("аБвг аБвг").titleize
+ end
+
+ def test_titleize_should_not_affect_characters_that_do_not_case_fold
+ assert_equal "日本語", chars("日本語").titleize
+ end
+
def test_limit_should_not_break_on_blank_strings
example = chars('')
assert_equal example, example.limit(0)
diff --git a/bin/rails b/bin/rails
index 4a74726e9b..a628cc35ca 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,3 +1,5 @@
+#!/usr/bin/env ruby
+
begin
require "rails/cli"
rescue LoadError
diff --git a/doc/template/horo.rb b/doc/template/horo.rb
index e028422a2e..b38fa28cde 100644
--- a/doc/template/horo.rb
+++ b/doc/template/horo.rb
@@ -11,8 +11,12 @@ if defined?(RDoc::Diagram)
end
end
+require 'rdoc/generator/html'
+
module RDoc
-module Page
+module Generator
+class HTML
+class HORO
FONTS = "\"Bitstream Vera Sans\", Verdana, Arial, Helvetica, sans-serif"
@@ -28,7 +32,7 @@ a:hover {
}
body, td, p {
- font-family: %fonts%;
+ font-family: <%= values['fonts'] %>;
background: #FFF;
color: #000;
margin: 0px;
@@ -206,7 +210,7 @@ dd {
}
CSS
-XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?>
+XHTML_PREAMBLE = %{<?xml version="1.0" encoding="<%= values['charset'] %>"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@@ -221,9 +225,9 @@ XHTML_FRAMESET_PREAMBLE = %{
HEADER = XHTML_PREAMBLE + <<ENDHEADER
<html>
<head>
- <title>%title%</title>
- <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
- <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
+ <title><%= values['title'] %></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=<%= values['charset'] %>" />
+ <link rel="stylesheet" href="<%= values['style_url'] %>" type="text/css" media="screen" />
<script language="JavaScript" type="text/javascript">
// <![CDATA[
@@ -273,20 +277,20 @@ FILE_PAGE = <<HTML
<table border='0' cellpadding='0' cellspacing='0' width="100%" class='banner'>
<tr><td>
<table width="100%" border='0' cellpadding='0' cellspacing='0'><tr>
- <td class="file-title" colspan="2"><span class="file-title-prefix">File</span><br />%short_name%</td>
+ <td class="file-title" colspan="2"><span class="file-title-prefix">File</span><br /><%= values['short_name'] %></td>
<td align="right">
<table border='0' cellspacing="0" cellpadding="2">
<tr>
<td>Path:</td>
- <td>%full_path%
-IF:cvsurl
- &nbsp;(<a href="%cvsurl%">CVS</a>)
-ENDIF:cvsurl
+ <td><%= values['full_path'] %>
+<% if values['cvsurl'] %>
+ &nbsp;(<a href="<%= values['cvsurl'] %>">CVS</a>)
+<% end %>
</td>
</tr>
<tr>
<td>Modified:</td>
- <td>%dtm_modified%</td>
+ <td><%= values['dtm_modified'] %></td>
</tr>
</table>
</td></tr>
@@ -299,34 +303,34 @@ HTML
CLASS_PAGE = <<HTML
<table width="100%" border='0' cellpadding='0' cellspacing='0' class='banner'><tr>
- <td class="file-title"><span class="file-title-prefix">%classmod%</span><br />%full_name%</td>
+ <td class="file-title"><span class="file-title-prefix"><%= values['classmod'] %></span><br /><%= values['full_name'] %></td>
<td align="right">
<table cellspacing="0" cellpadding="2">
<tr valign="top">
<td>In:</td>
<td>
-START:infiles
-HREF:full_path_url:full_path:
-IF:cvsurl
-&nbsp;(<a href="%cvsurl%">CVS</a>)
-ENDIF:cvsurl
-END:infiles
+<% values['infiles'].each do |infile| %>
+<%= href infile['full_path_url'], infile['full_path'] %>:
+<% if infile['cvsurl'] %>
+&nbsp;(<a href="<%= infile['cvsurl'] %>">CVS</a>)
+<% end %>
+<% end %>
</td>
</tr>
-IF:parent
+<% if values['parent'] %>
<tr>
<td>Parent:</td>
<td>
-IF:par_url
- <a href="%par_url%">
-ENDIF:par_url
-%parent%
-IF:par_url
+<% if values['par_url'] %>
+ <a href="<%= values['par_url'] %>">
+<% end %>
+<%= values['parent'] %>
+<% if values['par_url'] %>
</a>
-ENDIF:par_url
+<% end %>
</td>
</tr>
-ENDIF:parent
+<% end %>
</table>
</td>
</tr>
@@ -337,149 +341,149 @@ HTML
METHOD_LIST = <<HTML
<div id="content">
-IF:diagram
+<% if values['diagram'] %>
<table cellpadding='0' cellspacing='0' border='0' width="100%"><tr><td align="center">
- %diagram%
+ <%= values['diagram'] %>
</td></tr></table>
-ENDIF:diagram
+<% end %>
-IF:description
- <div class="description">%description%</div>
-ENDIF:description
+<% if values['description'] %>
+ <div class="description"><%= values['description'] %></div>
+<% end %>
-IF:requires
+<% if values['requires'] %>
<div class="sectiontitle">Required Files</div>
<ul>
-START:requires
- <li>HREF:aref:name:</li>
-END:requires
+<% values['requires'].each do |require| %>
+ <li><%= href require['aref'], require['name'] %>:</li>
+<% end %>
</ul>
-ENDIF:requires
+<% end %>
-IF:toc
+<% if values['toc'] %>
<div class="sectiontitle">Contents</div>
<ul>
-START:toc
- <li><a href="#%href%">%secname%</a></li>
-END:toc
+<% values['toc'].each do |toc| %>
+ <li><a href="#<%= toc['href'] %>"><%= toc['secname'] %></a></li>
+<% end %>
</ul>
-ENDIF:toc
+<% end %>
-IF:methods
+<% if values['methods'] %>
<div class="sectiontitle">Methods</div>
<ul>
-START:methods
- <li>HREF:aref:name:</li>
-END:methods
+<% values['methods'].each do |method| %>
+ <li><%= href method['aref'], method['name'] %></li>
+<% end %>
</ul>
-ENDIF:methods
+<% end %>
-IF:includes
+<% if values['includes'] %>
<div class="sectiontitle">Included Modules</div>
<ul>
-START:includes
- <li>HREF:aref:name:</li>
-END:includes
+<% values['includes'].each do |include| %>
+ <li><%= href include['aref'], include['name'] %>:</li>
+<% end %>
</ul>
-ENDIF:includes
+<% end %>
-START:sections
-IF:sectitle
-<div class="sectiontitle"><a name="%secsequence%">%sectitle%</a></div>
-IF:seccomment
+<% values['sections'].each do |section| %>
+<% if section['sectitle'] %>
+<div class="sectiontitle"><a name="<%= section['secsequence'] %>"><%= section['sectitle'] %></a></div>
+<% if section['seccomment'] %>
<div class="description">
-%seccomment%
+<%= section['seccomment'] %>
</div>
-ENDIF:seccomment
-ENDIF:sectitle
+<% end %>
+<% end %>
-IF:classlist
+<% if section['classlist'] %>
<div class="sectiontitle">Classes and Modules</div>
- %classlist%
-ENDIF:classlist
+ <%= section['classlist'] %>
+<% end %>
-IF:constants
+<% if section['constants'] %>
<div class="sectiontitle">Constants</div>
<table border='0' cellpadding='5'>
-START:constants
+<% section['constants'].each do |constant| %>
<tr valign='top'>
- <td class="attr-name">%name%</td>
+ <td class="attr-name"><%= constant['name'] %></td>
<td>=</td>
- <td class="attr-value">%value%</td>
+ <td class="attr-value"><%= constant['value'] %></td>
</tr>
-IF:desc
+<% if constant['desc'] %>
<tr valign='top'>
<td>&nbsp;</td>
- <td colspan="2" class="attr-desc">%desc%</td>
+ <td colspan="2" class="attr-desc"><%= constant['desc'] %></td>
</tr>
-ENDIF:desc
-END:constants
+<% end %>
+<% end %>
</table>
-ENDIF:constants
+<% end %>
-IF:attributes
+<% if section['attributes'] %>
<div class="sectiontitle">Attributes</div>
<table border='0' cellpadding='5'>
-START:attributes
+<% section['attributes'].each do |attribute| %>
<tr valign='top'>
<td class='attr-rw'>
-IF:rw
-[%rw%]
-ENDIF:rw
+<% if attribute['rw'] %>
+[<%= attribute['rw'] %>]
+<% end %>
</td>
- <td class='attr-name'>%name%</td>
- <td class='attr-desc'>%a_desc%</td>
+ <td class='attr-name'><%= attribute['name'] %></td>
+ <td class='attr-desc'><%= attribute['a_desc'] %></td>
</tr>
-END:attributes
+<% end %>
</table>
-ENDIF:attributes
+<% end %>
-IF:method_list
-START:method_list
-IF:methods
-<div class="sectiontitle">%type% %category% methods</div>
-START:methods
+<% if section['method_list'] %>
+<% section['method_list'].each do |method_list| %>
+<% if method_list['methods'] %>
+<div class="sectiontitle"><%= method_list['type'] %> <%= method_list['category'] %> methods</div>
+<% method_list['methods'].each do |method| %>
<div class="method">
<div class="title">
-IF:callseq
- <a name="%aref%"></a><b>%callseq%</b>
-ENDIF:callseq
-IFNOT:callseq
- <a name="%aref%"></a><b>%name%</b>%params%
-ENDIF:callseq
-IF:codeurl
-[&nbsp;<a href="%codeurl%" target="SOURCE_CODE" onclick="javascript:openCode('%codeurl%'); return false;">source</a>&nbsp;]
-ENDIF:codeurl
+<% if method['callseq'] %>
+ <a name="<%= method['aref'] %>"></a><b><%= method['callseq'] %></b>
+<% end %>
+<% unless method['callseq'] %>
+ <a name="<%= method['aref'] %>"></a><b><%= method['name'] %></b><%= method['params'] %>
+<% end %>
+<% if method['codeurl'] %>
+[&nbsp;<a href="<%= method['codeurl'] %>" target="SOURCE_CODE" onclick="javascript:openCode('<%= method['codeurl'] %>'); return false;">source</a>&nbsp;]
+<% end %>
</div>
-IF:m_desc
+<% if method['m_desc'] %>
<div class="description">
- %m_desc%
+ <%= method['m_desc'] %>
</div>
-ENDIF:m_desc
-IF:aka
+<% end %>
+<% if method['aka'] %>
<div class="aka">
This method is also aliased as
-START:aka
- <a href="%aref%">%name%</a>
-END:aka
+<% method['aka'].each do |aka| %>
+ <a href="<%= aka['aref'] %>"><%= aka['name'] %></a>
+<% end %>
</div>
-ENDIF:aka
-IF:sourcecode
+<% end %>
+<% if method['sourcecode'] %>
<div class="sourcecode">
- <p class="source-link">[ <a href="javascript:toggleSource('%aref%_source')" id="l_%aref%_source">show source</a> ]</p>
- <div id="%aref%_source" class="dyn-source">
+ <p class="source-link">[ <a href="javascript:toggleSource('<%= method['aref'] %>_source')" id="l_<%= method['aref'] %>_source">show source</a> ]</p>
+ <div id="<%= method['aref'] %>_source" class="dyn-source">
<pre>
-%sourcecode%
+<%= method['sourcecode'] %>
</pre>
</div>
</div>
-ENDIF:sourcecode
+<% end %>
</div>
-END:methods
-ENDIF:methods
-END:method_list
-ENDIF:method_list
-END:sections
+<% end %>
+<% end %>
+<% end %>
+<% end %>
+<% end %>
</div>
HTML
@@ -489,7 +493,7 @@ FOOTER = <<ENDFOOTER
ENDFOOTER
BODY = HEADER + <<ENDBODY
- !INCLUDE! <!-- banner header -->
+ <%= template_include %> <!-- banner header -->
<div id="bodyContent">
#{METHOD_LIST}
@@ -502,8 +506,8 @@ ENDBODY
SRC_PAGE = XHTML_PREAMBLE + <<HTML
<html>
-<head><title>%title%</title>
-<meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+<head><title><%= values['title'] %></title>
+<meta http-equiv="Content-Type" content="text/html; charset=<%= values['charset'] %>" />
<style type="text/css">
.ruby-comment { color: green; font-style: italic }
.ruby-constant { color: #4433aa; font-weight: bold; }
@@ -521,7 +525,7 @@ SRC_PAGE = XHTML_PREAMBLE + <<HTML
</style>
</head>
<body bgcolor="white">
-<pre>%code%</pre>
+<pre><%= values['code'] %></pre>
</body>
</html>
HTML
@@ -529,13 +533,13 @@ HTML
########################## Index ################################
FR_INDEX_BODY = <<HTML
-!INCLUDE!
+<%= template_include %>
HTML
FILE_INDEX = XHTML_PREAMBLE + <<HTML
<html>
<head>
-<meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+<meta http-equiv="Content-Type" content="text/html; charset=<%= values['charset'] %>" />
<title>Index</title>
<style type="text/css">
<!--
@@ -571,11 +575,11 @@ FILE_INDEX = XHTML_PREAMBLE + <<HTML
<base target="docwin" />
</head>
<body>
-<div class="banner">%list_title%</div>
+<div class="banner"><%= values['list_title'] %></div>
<div class="entries">
-START:entries
-<a href="%href%">%name%</a><br />
-END:entries
+<% values['entries'].each do |entrie| %>
+<a href="<%= entrie['href'] %>"><%= entrie['name'] %></a><br />
+<% end %>
</div>
</body></html>
HTML
@@ -586,8 +590,8 @@ METHOD_INDEX = FILE_INDEX
INDEX = XHTML_FRAMESET_PREAMBLE + <<HTML
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
- <title>%title%</title>
- <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <title><%= values['title'] %></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=<%= values['charset'] %>" />
</head>
<frameset cols="20%,*">
@@ -596,7 +600,7 @@ INDEX = XHTML_FRAMESET_PREAMBLE + <<HTML
<frame src="fr_class_index.html" name="Classes" />
<frame src="fr_method_index.html" name="Methods" />
</frameset>
- <frame src="%initial_page%" name="docwin" />
+ <frame src="<%= values['initial_page'] %>" name="docwin" />
<noframes>
<body bgcolor="white">
Click <a href="html/index.html">here</a> for a non-frames
@@ -610,4 +614,5 @@ HTML
end
end
-
+end
+end
diff --git a/railties/Rakefile b/railties/Rakefile
index efdb31cbd8..bf19961b59 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -1,3 +1,5 @@
+gem 'rdoc', '= 2.2'
+require 'rdoc'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
diff --git a/railties/guides/assets/javascripts/code_highlighter.js b/railties/guides/assets/javascripts/code_highlighter.js
index ce983dad52..ce983dad52 100755..100644
--- a/railties/guides/assets/javascripts/code_highlighter.js
+++ b/railties/guides/assets/javascripts/code_highlighter.js
diff --git a/railties/guides/assets/javascripts/guides.js b/railties/guides/assets/javascripts/guides.js
index c4e4d459ea..c4e4d459ea 100755..100644
--- a/railties/guides/assets/javascripts/guides.js
+++ b/railties/guides/assets/javascripts/guides.js
diff --git a/railties/guides/assets/stylesheets/print.css b/railties/guides/assets/stylesheets/print.css
index 628da105d4..628da105d4 100755..100644
--- a/railties/guides/assets/stylesheets/print.css
+++ b/railties/guides/assets/stylesheets/print.css
diff --git a/railties/guides/assets/stylesheets/reset.css b/railties/guides/assets/stylesheets/reset.css
index cb14fbcc55..cb14fbcc55 100755..100644
--- a/railties/guides/assets/stylesheets/reset.css
+++ b/railties/guides/assets/stylesheets/reset.css
diff --git a/railties/guides/assets/stylesheets/style.css b/railties/guides/assets/stylesheets/style.css
index 89b2ab885a..89b2ab885a 100755..100644
--- a/railties/guides/assets/stylesheets/style.css
+++ b/railties/guides/assets/stylesheets/style.css
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 85ae8cbbb1..8b8ef20b1f 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/hash/reverse_merge'
+require 'active_support/file_update_checker'
require 'fileutils'
require 'rails/plugin'
require 'rails/engine'
@@ -46,7 +47,6 @@ module Rails
autoload :Configuration, 'rails/application/configuration'
autoload :Finisher, 'rails/application/finisher'
autoload :Railties, 'rails/application/railties'
- autoload :RoutesReloader, 'rails/application/routes_reloader'
class << self
private :new
@@ -84,17 +84,30 @@ module Rails
delegate :middleware, :to => :config
- def add_lib_to_load_paths!
+ # This method is called just after an application inherits from Rails::Application,
+ # allowing the developer to load classes in lib and use them during application
+ # configuration.
+ #
+ # class MyApplication < Rails::Application
+ # require "my_backend" # in lib/my_backend
+ # config.i18n.backend = MyBackend
+ # end
+ #
+ # Notice this method takes into consideration the default root path. So if you
+ # are changing config.root inside your application definition or having a custom
+ # Rails application, you will need to add lib to $LOAD_PATH on your own in case
+ # you need to load files in lib/ during the application configuration as well.
+ def add_lib_to_load_paths! #:nodoc:
path = config.root.join('lib').to_s
$LOAD_PATH.unshift(path) if File.exists?(path)
end
- def require_environment!
+ def require_environment! #:nodoc:
environment = paths.config.environment.to_a.first
require environment if environment
end
- def eager_load!
+ def eager_load! #:nodoc:
railties.all(&:eager_load!)
super
end
@@ -108,11 +121,18 @@ module Rails
end
def routes_reloader
- @routes_reloader ||= RoutesReloader.new
+ @routes_reloader ||= ActiveSupport::FileUpdateChecker.new([]){ reload_routes! }
end
def reload_routes!
- routes_reloader.reload!
+ routes = Rails::Application.routes
+ routes.disable_clear_and_finalize = true
+
+ routes.clear!
+ routes_reloader.paths.each { |path| load(path) }
+ ActiveSupport.on_load(:action_controller) { routes.finalize! }
+ ensure
+ routes.disable_clear_and_finalize = false
end
def initialize!
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 0a435f0f36..44e26b5713 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -1,4 +1,5 @@
require "active_support/notifications"
+require "active_support/descendants_tracker"
module Rails
class Application
@@ -55,6 +56,7 @@ module Rails
initializer :set_clear_dependencies_hook do
unless config.cache_classes
ActionDispatch::Callbacks.after do
+ ActiveSupport::DescendantsTracker.clear
ActiveSupport::Dependencies.clear
end
end
diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb
deleted file mode 100644
index a2b3622df8..0000000000
--- a/railties/lib/rails/application/routes_reloader.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-module Rails
- class Application
- class RoutesReloader
- attr_reader :paths
-
- def initialize
- @paths, @last_change_at = [], nil
- end
-
- def changed_at
- routes_changed_at = nil
-
- paths.each do |path|
- config_changed_at = File.stat(path).mtime
-
- if routes_changed_at.nil? || config_changed_at > routes_changed_at
- routes_changed_at = config_changed_at
- end
- end
-
- routes_changed_at
- end
-
- def reload!
- routes = Rails::Application.routes
- routes.disable_clear_and_finalize = true
-
- routes.clear!
- paths.each { |path| load(path) }
- ActiveSupport.on_load(:action_controller) { routes.finalize! }
-
- nil
- ensure
- routes.disable_clear_and_finalize = false
- end
-
- def reload_if_changed
- current_change_at = changed_at
- if @last_change_at != current_change_at
- @last_change_at = current_change_at
- reload!
- end
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
index 1b3eef504a..1b3eef504a 100755..100644
--- a/railties/lib/rails/commands/generate.rb
+++ b/railties/lib/rails/commands/generate.rb
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 3f74cd49fc..cb9b871875 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -83,7 +83,8 @@ module Rails
:environment => (ENV['RAILS_ENV'] || "development").dup,
:daemonize => false,
:debugger => false,
- :pid => "tmp/pids/server.pid"
+ :pid => File.expand_path("tmp/pids/server.pid"),
+ :config => File.expand_path("config.ru")
})
end
end
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
index 5bc507ffc8..415f820206 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
@@ -3,4 +3,4 @@
<%%= render 'form' %>
<%%= link_to 'Show', @<%= singular_name %> %> |
-<%%= link_to 'Back', <%= plural_name %>_path %>
+<%%= link_to 'Back', <%= index_helper %>_path %>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
index 9a1c489331..ddabc9d349 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
@@ -2,4 +2,4 @@
<%%= render 'form' %>
-<%%= link_to 'Back', <%= plural_name %>_path %>
+<%%= link_to 'Back', <%= index_helper %>_path %>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
index 6b3518717a..31b8253b35 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
@@ -9,4 +9,4 @@
<% end -%>
<%%= link_to 'Edit', edit_<%= singular_name %>_path(@<%= singular_name %>) %> |
-<%%= link_to 'Back', <%= plural_name %>_path %>
+<%%= link_to 'Back', <%= index_helper %>_path %>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 8d1dfbd947..72ec2856d0 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -46,6 +46,14 @@ module Rails
end
end
+ def uncountable?
+ singular_name == plural_name
+ end
+
+ def index_helper
+ uncountable? ? "#{plural_name}_index" : plural_name
+ end
+
# Tries to retrieve the application name or simple return application.
def application_name
if defined?(Rails) && Rails.application
diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README
index 9ec6db6d71..e2764dee03 100644
--- a/railties/lib/rails/generators/rails/app/templates/README
+++ b/railties/lib/rails/generators/rails/app/templates/README
@@ -37,7 +37,7 @@ link:files/vendor/rails/actionpack/README.html.
3. Go to http://localhost:3000/ and you'll see:
"Welcome aboard: You're riding the Rails!"
-4. Follow the guidelines to start developing your application. You can find
+4. Follow the guidelines to start developing your application. You can find
the following resources handy:
* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
@@ -71,13 +71,13 @@ The result will be a message in your log file along the lines of:
More information on how to use the logger is at http://www.ruby-doc.org/core/
-Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
+Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
several books available online as well:
* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
-These two books will bring you up to speed on the Ruby language and also on
+These two books will bring you up to speed on the Ruby language and also on
programming in general.
@@ -199,7 +199,7 @@ app/controllers
ApplicationController which itself descends from ActionController::Base.
app/models
- Holds models that should be named like post.rb. Models descend from
+ Holds models that should be named like post.rb. Models descend from
ActiveRecord::Base by default.
app/views
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 13f1f9fa41..13f1f9fa41 100755..100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
index 2784a949fb..df5ef33064 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
@@ -32,40 +32,52 @@
# please refer to the latest documents at http://rubyforge.org/docman/?group_id=2361
development:
- adapter: ibm_db
- username: db2inst1
+ adapter: ibm_db
+ username: db2inst1
password:
- database: <%= app_name[0,4] %>_dev
- #schema: db2inst1
- #host: localhost
- #port: 50000
- #account: my_account
- #app_user: my_app_user
+ database: <%= app_name[0,4] %>_dev
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
#application: my_application
#workstation: my_workstation
+ #security: SSL
+ #timeout: 10
+ #authentication: SERVER
+ #parameterized: false
test:
- adapter: ibm_db
- username: db2inst1
+ adapter: ibm_db
+ username: db2inst1
password:
- database: <%= app_name[0,4] %>_tst
- #schema: db2inst1
- #host: localhost
- #port: 50000
- #account: my_account
- #app_user: my_app_user
+ database: <%= app_name[0,4] %>_tst
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
#application: my_application
#workstation: my_workstation
+ #security: SSL
+ #timeout: 10
+ #authentication: SERVER
+ #parameterized: false
production:
- adapter: ibm_db
- username: db2inst1
+ adapter: ibm_db
+ username: db2inst1
password:
- database: <%= app_name[0,8] %>
- #schema: db2inst1
- #host: localhost
- #port: 50000
- #account: my_account
- #app_user: my_app_user
+ database: <%= app_name[0,8] %>
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
#application: my_application
#workstation: my_workstation
+ #security: SSL
+ #timeout: 10
+ #authentication: SERVER
+ #parameterized: false \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
index 6bf2f7b1fd..ffc8a0a8cb 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -52,7 +52,7 @@ production:
database: <%= app_name %>_production
pool: 5
username: root
- password:
+ password:
<% if mysql_socket -%>
socket: <%= mysql_socket %>
<% else -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
index a1883f6256..f99ee937f3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
@@ -36,4 +36,4 @@ production:
adapter: oracle
database: <%= app_name %>_production
username: <%= app_name %>
- password:
+ password:
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
index d531b8bb82..9e8b0131f8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
@@ -1,6 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Add new inflection rules using the following format
+# Add new inflection rules using the following format
# (all these examples are active by default):
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
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 c2fa31aadb..22aa576f5d 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
@@ -2,6 +2,6 @@
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
-# Make sure the secret is at least 30 characters and all random,
+# 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 %>'
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index bbdce669dc..b5f19b6d15 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -78,7 +78,7 @@ class <%= controller_class_name %>Controller < ApplicationController
@<%= orm_instance.destroy %>
respond_to do |format|
- format.html { redirect_to(<%= table_name %>_url) }
+ format.html { redirect_to(<%= index_helper %>_url) }
format.xml { head :ok }
end
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index 4f8ddbffcf..d5d3d6d5cd 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -46,6 +46,6 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
delete :destroy, :id => @<%= file_name %>.to_param
end
- assert_redirected_to <%= table_name %>_path
+ assert_redirected_to <%= index_helper %>_path
end
end
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 99b2d86013..a1fcba3310 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -8,48 +8,143 @@ module ApplicationTests
build_app
boot_rails
FileUtils.rm_rf "#{app_path}/config/environments"
+ require "rails/all"
end
- # i18n
+ def load_app
+ require "#{app_path}/config/environment"
+ end
+
+ def app
+ @app ||= Rails::Application
+ end
+
+ def assert_fallbacks(fallbacks)
+ fallbacks.each do |locale, expected|
+ actual = I18n.fallbacks[locale]
+ assert_equal expected, actual, "expected fallbacks for #{locale.inspect} to be #{expected.inspect}, but were #{actual.inspect}"
+ end
+ end
+
+ def assert_no_fallbacks
+ assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ end
+
+ # Locales
test "setting another default locale" do
add_to_config <<-RUBY
- config.root = "#{app_path}"
config.i18n.default_locale = :de
RUBY
- require "#{app_path}/config/environment"
+ load_app
assert_equal :de, I18n.default_locale
end
+ # Load paths
test "no config locales dir present should return empty load path" do
FileUtils.rm_rf "#{app_path}/config/locales"
- add_to_config <<-RUBY
- config.root = "#{app_path}"
- RUBY
- require "#{app_path}/config/environment"
-
+ load_app
assert_equal [], Rails.application.config.i18n.load_path
end
- test "config locales dir present should be added to load path" do
+ test "locale files should be added to the load path" do
+ app_file "config/another_locale.yml", ""
+
add_to_config <<-RUBY
- config.root = "#{app_path}"
+ config.i18n.load_path << config.root.join("config/another_locale.yml").to_s
RUBY
- require "#{app_path}/config/environment"
- assert_equal ["#{app_path}/config/locales/en.yml"], Rails.application.config.i18n.load_path
+ load_app
+ assert_equal [
+ "#{app_path}/config/locales/en.yml", "#{app_path}/config/another_locale.yml"
+ ], Rails.application.config.i18n.load_path
+
+ assert I18n.load_path.include?("#{app_path}/config/locales/en.yml")
+ assert I18n.load_path.include?("#{app_path}/config/another_locale.yml")
end
- test "config defaults should be added with config settings" do
+ test "locales are reloaded if they change between requests" do
add_to_config <<-RUBY
- config.root = "#{app_path}"
- config.i18n.load_path << "my/other/locale.yml"
+ config.cache_classes = false
RUBY
- require "#{app_path}/config/environment"
- assert_equal [
- "#{app_path}/config/locales/en.yml", "my/other/locale.yml"
- ], Rails.application.config.i18n.load_path
+ app_file "config/locales/en.yml", <<-YAML
+en:
+ foo: "1"
+ YAML
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+ load_app
+
+ get "/i18n"
+ assert_equal "1", last_response.body
+
+ app_file "config/locales/en.yml", <<-YAML
+en:
+ foo: "2"
+ YAML
+
+ get "/i18n"
+ assert_equal "2", last_response.body
+ end
+
+ # Fallbacks
+ test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do
+ I18n.backend = Class.new { include I18n::Backend::Base }.new
+ load_app
+ assert_no_fallbacks
+ end
+
+ test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do
+ I18n::Railtie.config.i18n.fallbacks = true
+ load_app
+ assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ assert_fallbacks :de => [:de, :en]
+ end
+
+ test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do
+ I18n::Railtie.config.i18n.fallbacks = true
+ I18n::Railtie.config.i18n.backend = Class.new { include I18n::Backend::Base }.new
+ load_app
+ assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ assert_fallbacks :de => [:de, :en]
+ end
+
+ test "config.i18n.fallbacks.defaults = [:'en-US'] initializes fallbacks with en-US as a fallback default" do
+ I18n::Railtie.config.i18n.fallbacks.defaults = [:'en-US']
+ load_app
+ assert_fallbacks :de => [:de, :'en-US', :en]
+ end
+
+ test "config.i18n.fallbacks.map = { :ca => :'es-ES' } initializes fallbacks with a mapping ca => es-ES" do
+ I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
+ load_app
+ assert_fallbacks :ca => [:ca, :"es-ES", :es, :en]
+ end
+
+ test "[shortcut] config.i18n.fallbacks = [:'en-US'] initializes fallbacks with en-US as a fallback default" do
+ I18n::Railtie.config.i18n.fallbacks = [:'en-US']
+ load_app
+ assert_fallbacks :de => [:de, :'en-US', :en]
+ end
+
+ test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping de-AT => de-DE" do
+ I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
+ load_app
+ assert_fallbacks :ca => [:ca, :"es-ES", :es, :en]
+ end
+
+ test "[shortcut] config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] initializes fallbacks with the given arguments" do
+ I18n::Railtie.config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }]
+ load_app
+ assert_fallbacks :ca => [:ca, :"es-ES", :es, :'en-US', :en]
end
end
end \ No newline at end of file
diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb
index d31915e129..714d62311d 100644
--- a/railties/test/application/initializers/load_path_test.rb
+++ b/railties/test/application/initializers/load_path_test.rb
@@ -19,7 +19,7 @@ module ApplicationTests
assert $:.include?("#{app_path}/app/models")
end
- test "initializing an application adds lib path on inheritance hook" do
+ test "initializing an application allows to load code on lib path inside application class definitation" do
app_file "lib/foo.rb", <<-RUBY
module Foo; end
RUBY
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
new file mode 100644
index 0000000000..b337d3fc6e
--- /dev/null
+++ b/railties/test/application/loading_test.rb
@@ -0,0 +1,73 @@
+require 'isolation/abstract_unit'
+
+class LoadingTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ def test_load_should_load_constants
+ app_file "app/models/post.rb", <<-MODEL
+ class Post < ActiveRecord::Base
+ validates_acceptance_of :title, :accept => "omg"
+ end
+ MODEL
+
+ require "#{rails_root}/config/environment"
+ setup_ar!
+
+ p = Post.create(:title => 'omg')
+ assert_equal 1, Post.count
+ assert_equal 'omg', p.title
+ p = Post.first
+ assert_equal 'omg', p.title
+ end
+
+ def test_descendants_are_cleaned_on_each_request_without_cache_classes
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file "app/models/post.rb", <<-MODEL
+ class Post < ActiveRecord::Base
+ end
+ MODEL
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match '/load', :to => lambda { |env| [200, {}, Post.all] }
+ match '/unload', :to => lambda { |env| [200, {}, []] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ require "#{rails_root}/config/environment"
+ setup_ar!
+
+ assert_equal [], ActiveRecord::Base.descendants
+ get "/load"
+ assert_equal [Post], ActiveRecord::Base.descendants
+ get "/unload"
+ assert_equal [], ActiveRecord::Base.descendants
+ end
+
+ protected
+
+ def setup_ar!
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
+ ActiveRecord::Migration.verbose = false
+ ActiveRecord::Schema.define(:version => 1) do
+ create_table :posts do |t|
+ t.string :title
+ end
+ end
+ end
+end
diff --git a/railties/test/application/model_initialization_test.rb b/railties/test/application/model_initialization_test.rb
deleted file mode 100644
index 6a22f8d8df..0000000000
--- a/railties/test/application/model_initialization_test.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'isolation/abstract_unit'
-
-class PostTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- end
-
- def test_reload_should_reload_constants
- app_file "app/models/post.rb", <<-MODEL
- class Post < ActiveRecord::Base
- validates_acceptance_of :title, :accept => "omg"
- end
- MODEL
-
- require "#{rails_root}/config/environment"
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
- ActiveRecord::Migration.verbose = false
- ActiveRecord::Schema.define(:version => 1) do
- create_table :posts do |t|
- t.string :title
- end
- end
-
- p = Post.create(:title => 'omg')
- assert_equal 1, Post.count
- assert_equal 'omg', p.title
- p = Post.first
- assert_equal 'omg', p.title
- end
-end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 6b7a471494..40fb446b16 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -9,7 +9,7 @@ module ApplicationTests
boot_rails
FileUtils.rm_rf("#{app_path}/config/environments")
end
-
+
def test_gems_tasks_are_loaded_first_than_application_ones
app_file "lib/tasks/app.rake", <<-RUBY
$task_loaded = Rake::Task.task_defined?("db:create:all")
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index dcac1a87d9..f0268164d0 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -69,6 +69,20 @@ module ApplicationTests
assert_equal 'bar', last_response.body
end
+ test "mount rack app" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, :at => "/blog"
+ # The line below is required because mount sometimes
+ # fails when a resource route is added.
+ resource :user
+ end
+ RUBY
+
+ get '/blog/archives'
+ assert_equal '/archives', last_response.body
+ end
+
test "multiple controllers" do
controller :foo, <<-RUBY
class FooController < ApplicationController
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 6ea722e239..f9d1e42d24 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -62,4 +62,19 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove
+ migration = "create_books"
+ run_generator [migration, "title:string", "content:text"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_class_method :up, content do |up|
+ assert_match /^\s*$/, up
+ end
+
+ assert_class_method :down, content do |down|
+ assert_match /^\s*$/, down
+ end
+ end
+ end
end
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index e73dd237fb..1badae0713 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -93,6 +93,16 @@ class NamedBaseTest < Rails::Generators::TestCase
assert_name g, "application", :application_name
end
+ def test_index_helper
+ g = generator ['Post']
+ assert_name g, 'posts', :index_helper
+ end
+
+ def test_index_helper_with_uncountable
+ g = generator ['Sheep']
+ assert_name g, 'sheep_index', :index_helper
+ end
+
protected
def assert_name(generator, value, method)
diff --git a/railties/test/railties/i18n_railtie_test.rb b/railties/test/railties/i18n_railtie_test.rb
deleted file mode 100644
index 2b1950b3d5..0000000000
--- a/railties/test/railties/i18n_railtie_test.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require "isolation/abstract_unit"
-
-module RailtiesTest
- class I18nRailtieTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- FileUtils.rm_rf("#{app_path}/config/environments")
- require "rails/all"
- end
-
- def load_app
- require "#{app_path}/config/environment"
- end
-
- def assert_fallbacks(fallbacks)
- fallbacks.each do |locale, expected|
- actual = I18n.fallbacks[locale]
- assert_equal expected, actual, "expected fallbacks for #{locale.inspect} to be #{expected.inspect}, but were #{actual.inspect}"
- end
- end
-
- def assert_no_fallbacks
- assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
- end
-
- test "config.i18n.load_path gets added to I18n.load_path" do
- I18n.load_path = ['existing/path/to/locales']
- I18n::Railtie.config.i18n.load_path = ['new/path/to/locales']
- load_app
-
- assert I18n.load_path.include?('existing/path/to/locales')
- assert I18n.load_path.include?('new/path/to/locales')
- end
-
- test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do
- I18n.backend = Class.new { include I18n::Backend::Base }.new
- load_app
- assert_no_fallbacks
- end
-
- test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do
- I18n::Railtie.config.i18n.fallbacks = true
- load_app
- assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
- assert_fallbacks :de => [:de, :en]
- end
-
- test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do
- I18n::Railtie.config.i18n.fallbacks = true
- I18n::Railtie.config.i18n.backend = Class.new { include I18n::Backend::Base }.new
- load_app
- assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
- assert_fallbacks :de => [:de, :en]
- end
-
- test "config.i18n.fallbacks.defaults = [:'en-US'] initializes fallbacks with en-US as a fallback default" do
- I18n::Railtie.config.i18n.fallbacks.defaults = [:'en-US']
- load_app
- assert_fallbacks :de => [:de, :'en-US', :en]
- end
-
- test "config.i18n.fallbacks.map = { :ca => :'es-ES' } initializes fallbacks with a mapping ca => es-ES" do
- I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
- load_app
- assert_fallbacks :ca => [:ca, :"es-ES", :es, :en]
- end
-
- test "[shortcut] config.i18n.fallbacks = [:'en-US'] initializes fallbacks with en-US as a fallback default" do
- I18n::Railtie.config.i18n.fallbacks = [:'en-US']
- load_app
- assert_fallbacks :de => [:de, :'en-US', :en]
- end
-
- test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping de-AT => de-DE" do
- I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
- load_app
- assert_fallbacks :ca => [:ca, :"es-ES", :es, :en]
- end
-
- test "[shortcut] config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] initializes fallbacks with the given arguments" do
- I18n::Railtie.config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }]
- load_app
- assert_fallbacks :ca => [:ca, :"es-ES", :es, :'en-US', :en]
- end
- end
-end \ No newline at end of file