aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile7
-rw-r--r--actionmailer/CHANGELOG9
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb2
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb (renamed from actionmailer/lib/action_mailer/rails.rb)3
-rw-r--r--actionmailer/test/mail_layout_test.rb18
-rw-r--r--actionmailer/test/mail_service_test.rb88
-rw-r--r--actionmailer/test/quoting_test.rb2
-rw-r--r--actionmailer/test/test_helper_test.rb4
-rw-r--r--actionmailer/test/tmail_compat_test.rb4
-rw-r--r--actionpack/lib/abstract_controller/base.rb15
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb158
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb14
-rw-r--r--actionpack/lib/action_controller.rb16
-rw-r--r--actionpack/lib/action_controller/base.rb3
-rw-r--r--actionpack/lib/action_controller/metal.rb15
-rw-r--r--actionpack/lib/action_controller/metal/layouts.rb171
-rw-r--r--actionpack/lib/action_controller/metal/logger.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb12
-rw-r--r--actionpack/lib/action_controller/railtie.rb (renamed from actionpack/lib/action_controller/rails.rb)5
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb42
-rw-r--r--actionpack/lib/action_view/railtie.rb2
-rw-r--r--actionpack/lib/action_view/render/rendering.rb44
-rw-r--r--actionpack/test/controller/cookie_test.rb4
-rw-r--r--actionpack/test/lib/controller/fake_models.rb1
-rw-r--r--actionpack/test/template/form_helper_test.rb40
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb4
-rw-r--r--activemodel/lib/active_model/errors.rb11
-rw-r--r--activemodel/lib/active_model/lint.rb14
-rw-r--r--activemodel/lib/active_model/locale/en.yml3
-rw-r--r--activemodel/lib/active_model/railtie.rb2
-rw-r--r--activemodel/test/cases/lint_test.rb2
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb22
-rw-r--r--activemodel/test/cases/validations_test.rb6
-rw-r--r--activerecord/lib/active_record.rb1
-rwxr-xr-xactiverecord/lib/active_record/associations.rb88
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rwxr-xr-xactiverecord/lib/active_record/base.rb103
-rw-r--r--activerecord/lib/active_record/calculations.rb4
-rw-r--r--activerecord/lib/active_record/locale/en.yml3
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/named_scope.rb4
-rw-r--r--activerecord/lib/active_record/railtie.rb (renamed from activerecord/lib/active_record/rails.rb)6
-rw-r--r--activerecord/lib/active_record/relation.rb110
-rw-r--r--activerecord/lib/active_record/relation/calculation_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb45
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb19
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb17
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb6
-rw-r--r--activerecord/test/cases/autosave_association_test.rb31
-rwxr-xr-xactiverecord/test/cases/base_test.rb5
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb65
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb5
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb4
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb11
-rw-r--r--activerecord/test/cases/validations_repair_helper.rb20
-rw-r--r--activerecord/test/cases/validations_test.rb28
-rw-r--r--activerecord/test/models/reply.rb6
-rw-r--r--activeresource/lib/active_resource/railtie.rb2
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb1
-rw-r--r--activesupport/lib/active_support/cache.rb1
-rw-r--r--activesupport/lib/active_support/cache/compressed_mem_cache_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb1
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb77
-rw-r--r--activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/loading.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/synchronization.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/extending.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb1
-rw-r--r--activesupport/lib/active_support/duration.rb1
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb1
-rw-r--r--activesupport/lib/active_support/message_verifier.rb3
-rw-r--r--activesupport/lib/active_support/rescuable.rb2
-rw-r--r--activesupport/lib/active_support/testing/performance.rb1
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb1
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb110
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb84
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb51
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb82
-rw-r--r--activesupport/test/callback_inheritance_test.rb35
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb8
-rw-r--r--activesupport/test/xml_mini/libxml_engine_test.rb9
-rw-r--r--activesupport/test/xml_mini/libxmlsax_engine_test.rb194
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb40
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb217
-rw-r--r--railties/lib/rails.rb111
-rw-r--r--railties/lib/rails/all.rb15
-rw-r--r--railties/lib/rails/application.rb15
-rw-r--r--railties/lib/rails/configuration.rb36
-rw-r--r--railties/lib/rails/core.rb105
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/boot.rb11
-rw-r--r--railties/lib/rails/plugin.rb126
-rw-r--r--railties/lib/rails/railtie.rb43
-rw-r--r--railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb3
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/CHANGELOG.rdoc (renamed from railties/lib/rails/vendor/thor-0.12.1/CHANGELOG.rdoc)12
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/LICENSE (renamed from railties/lib/rails/vendor/thor-0.12.1/LICENSE)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/README.rdoc (renamed from railties/lib/rails/vendor/thor-0.12.1/README.rdoc)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/Thorfile (renamed from railties/lib/rails/vendor/thor-0.12.1/Thorfile)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor.rb)84
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions.rb)15
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/create_file.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/create_file.rb)4
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/directory.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/directory.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/empty_directory.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/empty_directory.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/file_manipulation.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/file_manipulation.rb)8
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/inject_into_file.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/inject_into_file.rb)4
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/base.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/base.rb)62
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/file_binary_read.rb9
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/hash_with_indifferent_access.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/hash_with_indifferent_access.rb)2
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/ordered_hash.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/ordered_hash.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/error.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/error.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/group.rb)46
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/invocation.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/invocation.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/argument.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/argument.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/arguments.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/arguments.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/option.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/option.rb)2
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/options.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/options.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/rake_compat.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/rake_compat.rb)0
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/runner.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/runner.rb)86
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell.rb)2
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/basic.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/basic.rb)76
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/color.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/color.rb)2
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/task.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/task.rb)75
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/util.rb (renamed from railties/lib/rails/vendor/thor-0.12.1/lib/thor/util.rb)26
-rw-r--r--railties/lib/rails/vendor/thor-0.12.3/lib/thor/version.rb3
-rw-r--r--railties/test/abstract_unit.rb2
-rw-r--r--railties/test/application/configuration_test.rb20
-rw-r--r--railties/test/application/generators_test.rb2
-rw-r--r--railties/test/application/initializer_test.rb6
-rw-r--r--railties/test/application/notifications_test.rb1
-rw-r--r--railties/test/generators/app_generator_test.rb9
-rw-r--r--railties/test/initializer/check_ruby_version_test.rb4
-rw-r--r--railties/test/initializer/path_test.rb4
-rw-r--r--railties/test/isolation/abstract_unit.rb2
-rw-r--r--railties/test/plugins/configuration_test.rb10
-rw-r--r--railties/test/plugins/framework_extension_test.rb6
153 files changed, 2113 insertions, 1259 deletions
diff --git a/Gemfile b/Gemfile
index 015c6ab918..2aca5065f5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -13,8 +13,11 @@ gem "i18n", ">= 0.3.0"
# AR
gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git"
gem "sqlite3-ruby", ">= 1.2.5"
-gem "pg", ">= 0.8.0"
-gem "mysql", ">= 2.8.1"
+
+only :test do
+ gem "pg", ">= 0.8.0"
+ gem "mysql", ">= 2.8.1"
+end
# AP
gem "rack", "1.1.0", :git => "git://github.com/rack/rack.git"
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG
index 6f4ba96844..785bf98c55 100644
--- a/actionmailer/CHANGELOG
+++ b/actionmailer/CHANGELOG
@@ -1,13 +1,14 @@
*Rails 3.0 (pending)*
-* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no reversing of ordering takes place. The default order now is text/plain, then text/enriched, then text/html and then any other part that is not one of these three.
+* The Mail::Message class has helped methods for all the field types that return 'common' defaults for the common use case, so to get the subject, mail.subject will give you a string, mail.date will give you a DateTime object, mail.from will give you an array of address specs (mikel@test.lindsaar.net) etc. If you want to access the field object itself, call mail[:field_name] which will return the field object you want, which you can then chain, like mail[:from].formatted
-* Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc
+* Mail#content_type now returns the content_type field as a string. If you want the mime type of a mail, then you call Mail#mime_type (eg, text/plain), if you want the parameters of the content type field, you call Mail#content_type_parameters which gives you a hash, eg {'format' => 'flowed', 'charset' => 'utf-8'}
-* Every part of a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call #encoded or #decoded to get the string you want.
+* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no reversing of ordering takes place. The default order now is text/plain, then text/enriched, then text/html and then any other part that is not one of these three.
-* By default, a field will return the #decoded value when you send it :to_s and any object that is a container (like header, body etc) will return #encoded value when you send it :to_s
+* Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc
+* Every object in a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call #encoded or #decoded to get the string you want.
* Mail::Message#set_content_type does not exist, it is simply Mail::Message#content_type
* Every mail message gets a unique message_id unless you specify one, had to change all the tests that check for equality with expected.encoded == actual.encoded to first replace their message_ids with control values
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 201b56a739..8adea46d35 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 3.0.pre')
- s.add_dependency('mail', '~> 1.4.3')
+ s.add_dependency('mail', '~> 1.5.0')
s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*']
s.has_rdoc = true
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index f34a9bae47..0248e29cb7 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -254,6 +254,7 @@ module ActionMailer #:nodoc:
include Quoting
extend AdvAttrAccessor
+ include AbstractController::Logger
include AbstractController::Rendering
include AbstractController::LocalizedCache
include AbstractController::Layouts
@@ -364,6 +365,7 @@ module ActionMailer #:nodoc:
def mailer_name
@mailer_name ||= name.underscore
end
+ alias :controller_path :mailer_name
def delivery_method=(method_name)
@delivery_method = ActionMailer::DeliveryMethod.lookup_method(method_name)
diff --git a/actionmailer/lib/action_mailer/rails.rb b/actionmailer/lib/action_mailer/railtie.rb
index a3573cdea7..5410c7d75f 100644
--- a/actionmailer/lib/action_mailer/rails.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -1,7 +1,8 @@
require "action_mailer"
+require "rails"
module ActionMailer
- class Plugin < Rails::Plugin
+ class Railtie < Rails::Railtie
plugin_name :action_mailer
initializer "action_mailer.set_configs" do |app|
diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb
index 84f13a6d3c..0877e7b2cb 100644
--- a/actionmailer/test/mail_layout_test.rb
+++ b/actionmailer/test/mail_layout_test.rb
@@ -72,12 +72,12 @@ class LayoutMailerTest < Test::Unit::TestCase
mail = AutoLayoutMailer.create_multipart(@recipient)
# CHANGED: content_type returns an object
# assert_equal "multipart/alternative", mail.content_type
- assert_equal "multipart/alternative", mail.content_type.string
+ assert_equal "multipart/alternative", mail.mime_type
assert_equal 2, mail.parts.size
# CHANGED: content_type returns an object
# assert_equal 'text/plain', mail.parts.first.content_type
- assert_equal 'text/plain', mail.parts.first.content_type.string
+ assert_equal 'text/plain', mail.parts.first.mime_type
# CHANGED: body returns an object
# assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
@@ -85,7 +85,7 @@ class LayoutMailerTest < Test::Unit::TestCase
# CHANGED: content_type returns an object
# assert_equal 'text/html', mail.parts.last.content_type
- assert_equal 'text/html', mail.parts.last.content_type.string
+ assert_equal 'text/html', mail.parts.last.mime_type
# CHANGED: body returns an object
# assert_equal "Hello from layout text/html multipart", mail.parts.last.body
@@ -96,19 +96,19 @@ class LayoutMailerTest < Test::Unit::TestCase
mail = AutoLayoutMailer.create_multipart(@recipient, "multipart/mixed")
# CHANGED: content_type returns an object
# assert_equal "multipart/mixed", mail.content_type
- assert_equal "multipart/mixed", mail.content_type.string
+ assert_equal "multipart/mixed", mail.mime_type
assert_equal 2, mail.parts.size
# CHANGED: content_type returns an object
# assert_equal 'text/plain', mail.parts.first.content_type
- assert_equal 'text/plain', mail.parts.first.content_type.string
+ assert_equal 'text/plain', mail.parts.first.mime_type
# CHANGED: body returns an object
# assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
# CHANGED: content_type returns an object
# assert_equal 'text/html', mail.parts.last.content_type
- assert_equal 'text/html', mail.parts.last.content_type.string
+ assert_equal 'text/html', mail.parts.last.mime_type
# CHANGED: body returns an object
# assert_equal "Hello from layout text/html multipart", mail.parts.last.body
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
@@ -116,13 +116,13 @@ class LayoutMailerTest < Test::Unit::TestCase
def test_should_fix_multipart_layout
mail = AutoLayoutMailer.create_multipart(@recipient, "text/plain")
- assert_equal "multipart/alternative", mail.content_type.string
+ assert_equal "multipart/alternative", mail.mime_type
assert_equal 2, mail.parts.size
- assert_equal 'text/plain', mail.parts.first.content_type.string
+ assert_equal 'text/plain', mail.parts.first.mime_type
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
- assert_equal 'text/html', mail.parts.last.content_type.string
+ assert_equal 'text/html', mail.parts.last.mime_type
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
end
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index 152900259d..f66b4a174b 100644
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -362,13 +362,13 @@ class ActionMailerTest < Test::Unit::TestCase
assert_equal 2, created.parts.size
assert_equal 2, created.parts.first.parts.size
- assert_equal "multipart/mixed", created.content_type.string
- assert_equal "multipart/alternative", created.parts[0].content_type.string
+ assert_equal "multipart/mixed", created.mime_type
+ assert_equal "multipart/alternative", created.parts[0].mime_type
assert_equal "bar", created.parts[0].header['foo'].to_s
assert_nil created.parts[0].charset
- assert_equal "text/plain", created.parts[0].parts[0].content_type.string
- assert_equal "text/html", created.parts[0].parts[1].content_type.string
- assert_equal "application/octet-stream", created.parts[1].content_type.string
+ assert_equal "text/plain", created.parts[0].parts[0].mime_type
+ assert_equal "text/html", created.parts[0].parts[1].mime_type
+ assert_equal "application/octet-stream", created.parts[1].mime_type
end
@@ -380,11 +380,11 @@ class ActionMailerTest < Test::Unit::TestCase
assert_equal 1,created.parts.size
assert_equal 2,created.parts.first.parts.size
- assert_equal "multipart/mixed", created.content_type.string
- assert_equal "multipart/alternative", created.parts.first.content_type.string
- assert_equal "text/plain", created.parts.first.parts.first.content_type.string
+ assert_equal "multipart/mixed", created.mime_type
+ assert_equal "multipart/alternative", created.parts.first.mime_type
+ assert_equal "text/plain", created.parts.first.parts.first.mime_type
assert_equal "Nothing to see here.", created.parts.first.parts.first.body.to_s
- assert_equal "text/html", created.parts.first.parts.second.content_type.string
+ assert_equal "text/html", created.parts.first.parts.second.mime_type
assert_equal "<b>test</b> HTML<br/>", created.parts.first.parts.second.body.to_s
end
@@ -468,8 +468,8 @@ class ActionMailerTest < Test::Unit::TestCase
assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
assert_not_nil created
assert_equal 2, created.parts.length
- assert_equal 'text/plain', created.parts[0].content_type.string
- assert_equal 'text/html', created.parts[1].content_type.string
+ assert_equal 'text/plain', created.parts[0].mime_type
+ assert_equal 'text/html', created.parts[1].mime_type
end
def test_cancelled_account
@@ -731,8 +731,8 @@ Content-Type: text/plain; charset=iso-8859-1
The body
EOF
mail = Mail.new(msg)
- assert_equal "testing testing \326\244", mail.subject.to_s
- assert_equal "Subject: =?utf-8?Q?testing_testing_=D6=A4?=\r\n", mail.subject.encoded
+ assert_equal "testing testing \326\244", mail.subject
+ assert_equal "Subject: =?utf-8?Q?testing_testing_=D6=A4?=\r\n", mail[:subject].encoded
end
def test_unquote_7bit_subject
@@ -744,8 +744,8 @@ Content-Type: text/plain; charset=iso-8859-1
The body
EOF
mail = Mail.new(msg)
- assert_equal "this == working?", mail.subject.to_s
- assert_equal "Subject: this == working?\r\n", mail.subject.encoded
+ assert_equal "this == working?", mail.subject
+ assert_equal "Subject: this == working?\r\n", mail[:subject].encoded
end
def test_unquote_7bit_body
@@ -868,7 +868,7 @@ EOF
mail = Mail.new(fixture)
attachment = mail.attachments.last
assert_equal "smime.p7s", attachment.original_filename
- assert_equal "application/pkcs7-signature", mail.parts.last.content_type.string
+ assert_equal "application/pkcs7-signature", mail.parts.last.mime_type
end
def test_decode_attachment_without_charset
@@ -913,7 +913,7 @@ EOF
def test_multipart_with_mime_version
mail = TestMailer.create_multipart_with_mime_version(@recipient)
- assert_equal "1.1", mail.mime_version.version
+ assert_equal "1.1", mail.mime_version
end
def test_multipart_with_utf8_subject
@@ -929,29 +929,29 @@ EOF
def test_explicitly_multipart_messages
mail = TestMailer.create_explicitly_multipart_example(@recipient)
assert_equal 3, mail.parts.length
- assert_equal 'multipart/mixed', mail.content_type.string
- assert_equal "text/plain", mail.parts[0].content_type.string
+ assert_equal 'multipart/mixed', mail.mime_type
+ assert_equal "text/plain", mail.parts[0].mime_type
- assert_equal "text/html", mail.parts[1].content_type.string
+ assert_equal "text/html", mail.parts[1].mime_type
assert_equal "iso-8859-1", mail.parts[1].charset
- assert_equal "image/jpeg", mail.parts[2].content_type.string
- assert_equal "attachment", mail.parts[2].content_disposition.disposition_type
- assert_equal "foo.jpg", mail.parts[2].content_disposition.filename
- assert_equal "foo.jpg", mail.parts[2].content_type.filename
+ assert_equal "image/jpeg", mail.parts[2].mime_type
+ assert_equal "attachment", mail.parts[2][:content_disposition].disposition_type
+ assert_equal "foo.jpg", mail.parts[2][:content_disposition].filename
+ assert_equal "foo.jpg", mail.parts[2][:content_type].filename
assert_nil mail.parts[2].charset
end
def test_explicitly_multipart_with_content_type
mail = TestMailer.create_explicitly_multipart_example(@recipient, "multipart/alternative")
assert_equal 3, mail.parts.length
- assert_equal "multipart/alternative", mail.content_type.string
+ assert_equal "multipart/alternative", mail.mime_type
end
def test_explicitly_multipart_with_invalid_content_type
mail = TestMailer.create_explicitly_multipart_example(@recipient, "text/xml")
assert_equal 3, mail.parts.length
- assert_equal 'multipart/mixed', mail.content_type.string
+ assert_equal 'multipart/mixed', mail.mime_type
end
def test_implicitly_multipart_messages
@@ -960,12 +960,12 @@ EOF
mail = TestMailer.create_implicitly_multipart_example(@recipient)
assert_equal 3, mail.parts.length
assert_equal "1.0", mail.mime_version.to_s
- assert_equal "multipart/alternative", mail.content_type.string
- assert_equal "text/plain", mail.parts[0].content_type.string
+ assert_equal "multipart/alternative", mail.mime_type
+ assert_equal "text/plain", mail.parts[0].mime_type
assert_equal "utf-8", mail.parts[0].charset
- assert_equal "text/html", mail.parts[1].content_type.string
+ assert_equal "text/html", mail.parts[1].mime_type
assert_equal "utf-8", mail.parts[1].charset
- assert_equal "application/x-yaml", mail.parts[2].content_type.string
+ assert_equal "application/x-yaml", mail.parts[2].mime_type
assert_equal "utf-8", mail.parts[2].charset
end
@@ -974,9 +974,9 @@ EOF
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["application/x-yaml", "text/plain"])
assert_equal 3, mail.parts.length
- assert_equal "application/x-yaml", mail.parts[0].content_type.string
- assert_equal "text/plain", mail.parts[1].content_type.string
- assert_equal "text/html", mail.parts[2].content_type.string
+ assert_equal "application/x-yaml", mail.parts[0].mime_type
+ assert_equal "text/plain", mail.parts[1].mime_type
+ assert_equal "text/html", mail.parts[2].mime_type
end
def test_implicitly_multipart_messages_with_charset
@@ -984,14 +984,14 @@ EOF
assert_equal "multipart/alternative", mail.header['content-type'].content_type
- assert_equal 'iso-8859-1', mail.parts[0].content_type.parameters[:charset]
- assert_equal 'iso-8859-1', mail.parts[1].content_type.parameters[:charset]
- assert_equal 'iso-8859-1', mail.parts[2].content_type.parameters[:charset]
+ assert_equal 'iso-8859-1', mail.parts[0].content_type_parameters[:charset]
+ assert_equal 'iso-8859-1', mail.parts[1].content_type_parameters[:charset]
+ assert_equal 'iso-8859-1', mail.parts[2].content_type_parameters[:charset]
end
def test_html_mail
mail = TestMailer.create_html_mail(@recipient)
- assert_equal "text/html", mail.content_type.string
+ assert_equal "text/html", mail.mime_type
end
def test_html_mail_with_underscores
@@ -1043,7 +1043,7 @@ EOF
assert_equal(4, mail.parts.first.parts.length)
assert_equal("This is the first part.", mail.parts.first.parts.first.body.to_s)
assert_equal("test.rb", mail.parts.first.parts.second.filename)
- assert_equal("flowed", mail.parts.first.parts.fourth.content_type.parameters[:format])
+ assert_equal("flowed", mail.parts.first.parts.fourth.content_type_parameters[:format])
assert_equal('smime.p7s', mail.parts.second.filename)
end
@@ -1081,9 +1081,9 @@ EOF
assert !mail.from_addrs.empty?
assert !mail.cc_addrs.empty?
assert !mail.bcc_addrs.empty?
- assert_match(/:/, mail.from_addrs.to_s)
- assert_match(/:/, mail.cc_addrs.to_s)
- assert_match(/:/, mail.bcc_addrs.to_s)
+ assert_match(/:/, mail[:from].decoded)
+ assert_match(/:/, mail[:cc].decoded)
+ assert_match(/:/, mail[:bcc].decoded)
end
def test_deliver_with_mail_object
@@ -1095,14 +1095,14 @@ EOF
def test_multipart_with_template_path_with_dots
mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
assert_equal 2, mail.parts.length
- assert "text/plain", mail.parts[1].content_type.string
+ assert "text/plain", mail.parts[1].mime_type
assert "utf-8", mail.parts[1].charset
end
def test_custom_content_type_attributes
mail = TestMailer.create_custom_content_type_attributes
- assert_match %r{format="flowed"}, mail.content_type.encoded
- assert_match %r{charset="utf-8"}, mail.content_type.encoded
+ assert_match %r{format=flowed}, mail.content_type
+ assert_match %r{charset=utf-8}, mail.content_type
end
def test_return_path_with_create
diff --git a/actionmailer/test/quoting_test.rb b/actionmailer/test/quoting_test.rb
index b16d160805..7640f4b086 100644
--- a/actionmailer/test/quoting_test.rb
+++ b/actionmailer/test/quoting_test.rb
@@ -78,7 +78,7 @@ class QuotingTest < Test::Unit::TestCase
mail = Mail.new(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_partially_quoted_subject"))
# CHANGED: subject returns an object now
# assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject
- assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject.decoded
+ assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject
end
private
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index 86d22da9bf..1fed26f78f 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -19,8 +19,8 @@ class TestHelperMailerTest < ActionMailer::TestCase
def test_setup_creates_the_expected_mailer
assert @expected.is_a?(Mail::Message)
- assert_equal "1.0", @expected.mime_version.version
- assert_equal "text/plain", @expected.content_type.string
+ assert_equal "1.0", @expected.mime_version
+ assert_equal "text/plain", @expected.mime_type
end
def test_mailer_class_is_correctly_inferred
diff --git a/actionmailer/test/tmail_compat_test.rb b/actionmailer/test/tmail_compat_test.rb
index faa267e3bf..a1ca6a7243 100644
--- a/actionmailer/test/tmail_compat_test.rb
+++ b/actionmailer/test/tmail_compat_test.rb
@@ -8,7 +8,7 @@ class TmailCompatTest < Test::Unit::TestCase
assert_nothing_raised do
mail.set_content_type "text/plain"
end
- assert_equal mail.content_type.string, "text/plain"
+ assert_equal mail.mime_type, "text/plain"
end
def test_transfer_encoding_raises_deprecation_warning
@@ -17,7 +17,7 @@ class TmailCompatTest < Test::Unit::TestCase
assert_nothing_raised do
mail.transfer_encoding "base64"
end
- assert_equal mail.content_transfer_encoding.value, "base64"
+ assert_equal mail.content_transfer_encoding, "base64"
end
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index efea81aa71..a6889d5d01 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -72,6 +72,16 @@ module AbstractController
# And always exclude explicitly hidden actions
hidden_actions
end
+
+ # Returns the full controller name, underscored, without the ending Controller.
+ # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
+ # controller_name.
+ #
+ # ==== Returns
+ # String
+ def controller_path
+ @controller_path ||= name && name.sub(/Controller$/, '').underscore
+ end
end
abstract!
@@ -96,6 +106,11 @@ module AbstractController
process_action(action_name, *args)
end
+ # Delegates to the class' #controller_path
+ def controller_path
+ self.class.controller_path
+ end
+
private
# Returns true if the name can be considered an action. This can
# be overridden in subclasses to modify the semantics of what
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 4073e9b386..6fbf6bc392 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -1,4 +1,160 @@
module AbstractController
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
+ # repeated setups. The inclusion pattern has pages that look like this:
+ #
+ # <%= render "shared/header" %>
+ # Hello World
+ # <%= render "shared/footer" %>
+ #
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
+ #
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
+ # that the header and footer are only mentioned in one place, like this:
+ #
+ # // The header part of this layout
+ # <%= yield %>
+ # // The footer part of this layout
+ #
+ # And then you have content pages that look like this:
+ #
+ # hello world
+ #
+ # At rendering time, the content page is computed and then inserted in the layout, like this:
+ #
+ # // The header part of this layout
+ # hello world
+ # // The footer part of this layout
+ #
+ # == Accessing shared variables
+ #
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
+ # references that won't materialize before rendering time:
+ #
+ # <h1><%= @page_title %></h1>
+ # <%= yield %>
+ #
+ # ...and content pages that fulfill these references _at_ rendering time:
+ #
+ # <% @page_title = "Welcome" %>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # The result after rendering is:
+ #
+ # <h1>Welcome</h1>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # == Layout assignment
+ #
+ # You can either specify a layout declaratively (using the #layout class method) or give
+ # it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
+ # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
+ #
+ # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
+ # that template will be used for all actions in PostsController and controllers inheriting
+ # from PostsController.
+ #
+ # If you use a module, for instance Weblog::PostsController, you will need a template named
+ # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
+ #
+ # Since all your controllers inherit from ApplicationController, they will use
+ # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
+ # or provided.
+ #
+ # == Inheritance Examples
+ #
+ # class BankController < ActionController::Base
+ # layout "bank_standard"
+ #
+ # class InformationController < BankController
+ #
+ # class TellerController < BankController
+ # # teller.html.erb exists
+ #
+ # class TillController < TellerController
+ #
+ # class VaultController < BankController
+ # layout :access_level_layout
+ #
+ # class EmployeeController < BankController
+ # layout nil
+ #
+ # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
+ #
+ # The TellerController uses +teller.html.erb+, and TillController inherits that layout and
+ # uses it as well.
+ #
+ # == Types of layouts
+ #
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
+ #
+ # The method reference is the preferred approach to variable layouts and is used like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout :writers_and_readers
+ #
+ # def index
+ # # fetching posts
+ # end
+ #
+ # private
+ # def writers_and_readers
+ # logged_in? ? "writer_layout" : "reader_layout"
+ # end
+ #
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
+ # is logged in or not.
+ #
+ # If you want to use an inline method, such as a proc, do something like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ #
+ # Of course, the most common way of specifying a layout is still just as a plain template name:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
+ # Otherwise, it will be looked up relative to the template root.
+ #
+ # == Conditional layouts
+ #
+ # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
+ # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
+ # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard", :except => :rss
+ #
+ # # ...
+ #
+ # end
+ #
+ # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
+ # around the rendered view.
+ #
+ # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
+ # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
+ #
+ # == Using a different layout in the action render call
+ #
+ # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
+ # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
+ # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # def help
+ # render :action => "help", :layout => "help"
+ # end
+ # end
+ #
+ # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
module Layouts
extend ActiveSupport::Concern
@@ -89,7 +245,7 @@ module AbstractController
# ==== Returns
# String:: A template name
def _implied_layout_name
- name && name.underscore
+ controller_path
end
# Takes the specified layout and creates a _layout method to be called
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 64a8a5f241..332d86b089 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,5 +1,4 @@
require "abstract_controller/base"
-require "abstract_controller/logger"
module AbstractController
class DoubleRenderError < Error
@@ -13,8 +12,6 @@ module AbstractController
module Rendering
extend ActiveSupport::Concern
- include AbstractController::Logger
-
included do
extlib_inheritable_accessor :_view_paths
self._view_paths ||= ActionView::PathSet.new
@@ -67,7 +64,7 @@ module AbstractController
def render_to_body(options = {})
# TODO: Refactor so we can just use the normal template logic for this
if options.key?(:partial)
- view_context.render_partial(options)
+ _render_partial(options)
else
_determine_template(options)
_render_template(options)
@@ -87,11 +84,18 @@ module AbstractController
# ==== Options
# _template<ActionView::Template>:: The template to render
# _layout<ActionView::Template>:: The layout to wrap the template in (optional)
- # _partial<TrueClass, FalseClass>:: Whether or not the template to be rendered is a partial
def _render_template(options)
view_context.render_template(options)
end
+ # Renders the given partial.
+ #
+ # ==== Options
+ # partial<String|Object>:: The partial name or the object to be rendered
+ def _render_partial(options)
+ view_context.render_partial(options)
+ end
+
# The list of view paths for this controller. See ActionView::ViewPathSet for
# more details about writing custom view paths.
def view_paths
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 26a85d4de8..d66fc3fcc9 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -13,31 +13,29 @@ module ActionController
autoload :Middleware
autoload_under "metal" do
- autoload :Benchmarking
+ autoload :Compatibility
autoload :ConditionalGet
autoload :Configuration
+ autoload :Cookies
+ autoload :FilterParameterLogging
+ autoload :Flash
autoload :Head
autoload :Helpers
autoload :HideActions
- autoload :Layouts
+ autoload :HttpAuthentication
autoload :Logger
autoload :MimeResponds
autoload :RackDelegation
- autoload :Compatibility
autoload :Redirecting
autoload :Rendering
autoload :Renderers
+ autoload :RequestForgeryProtection
autoload :Rescue
autoload :Responder
autoload :SessionManagement
+ autoload :Streaming
autoload :UrlFor
autoload :Verification
- autoload :Flash
- autoload :RequestForgeryProtection
- autoload :Streaming
- autoload :HttpAuthentication
- autoload :FilterParameterLogging
- autoload :Cookies
end
autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 67656110c4..b23be66910 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -3,7 +3,7 @@ module ActionController
abstract!
include AbstractController::Callbacks
- include AbstractController::Logger
+ include AbstractController::Layouts
include ActionController::Helpers
include ActionController::HideActions
@@ -11,7 +11,6 @@ module ActionController
include ActionController::Redirecting
include ActionController::Rendering
include ActionController::Renderers::All
- include ActionController::Layouts
include ActionController::ConditionalGet
include ActionController::RackDelegation
include ActionController::Logger
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index f445ca70ee..1819c0f886 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -28,21 +28,6 @@ module ActionController
self.class.controller_name
end
- # Returns the full controller name, underscored, without the ending Controller.
- # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
- # controller_name.
- #
- # ==== Returns
- # String
- def self.controller_path
- @controller_path ||= name && name.sub(/Controller$/, '').underscore
- end
-
- # Delegates to the class' #controller_path
- def controller_path
- self.class.controller_path
- end
-
# The details below can be overridden to support a specific
# Request and Response object. The default ActionController::Base
# implementation includes RackDelegation, which makes a request
diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb
deleted file mode 100644
index f44498a884..0000000000
--- a/actionpack/lib/action_controller/metal/layouts.rb
+++ /dev/null
@@ -1,171 +0,0 @@
-module ActionController
- # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
- # repeated setups. The inclusion pattern has pages that look like this:
- #
- # <%= render "shared/header" %>
- # Hello World
- # <%= render "shared/footer" %>
- #
- # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
- # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
- #
- # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
- # that the header and footer are only mentioned in one place, like this:
- #
- # // The header part of this layout
- # <%= yield %>
- # // The footer part of this layout
- #
- # And then you have content pages that look like this:
- #
- # hello world
- #
- # At rendering time, the content page is computed and then inserted in the layout, like this:
- #
- # // The header part of this layout
- # hello world
- # // The footer part of this layout
- #
- # == Accessing shared variables
- #
- # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
- # references that won't materialize before rendering time:
- #
- # <h1><%= @page_title %></h1>
- # <%= yield %>
- #
- # ...and content pages that fulfill these references _at_ rendering time:
- #
- # <% @page_title = "Welcome" %>
- # Off-world colonies offers you a chance to start a new life
- #
- # The result after rendering is:
- #
- # <h1>Welcome</h1>
- # Off-world colonies offers you a chance to start a new life
- #
- # == Layout assignment
- #
- # You can either specify a layout declaratively (using the #layout class method) or give
- # it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
- # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
- #
- # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
- # that template will be used for all actions in PostsController and controllers inheriting
- # from PostsController.
- #
- # If you use a module, for instance Weblog::PostsController, you will need a template named
- # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
- #
- # Since all your controllers inherit from ApplicationController, they will use
- # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
- # or provided.
- #
- # == Inheritance Examples
- #
- # class BankController < ActionController::Base
- # layout "bank_standard"
- #
- # class InformationController < BankController
- #
- # class TellerController < BankController
- # # teller.html.erb exists
- #
- # class TillController < TellerController
- #
- # class VaultController < BankController
- # layout :access_level_layout
- #
- # class EmployeeController < BankController
- # layout nil
- #
- # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
- # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
- #
- # The TellerController uses +teller.html.erb+, and TillController inherits that layout and
- # uses it as well.
- #
- # == Types of layouts
- #
- # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
- # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
- # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
- #
- # The method reference is the preferred approach to variable layouts and is used like this:
- #
- # class WeblogController < ActionController::Base
- # layout :writers_and_readers
- #
- # def index
- # # fetching posts
- # end
- #
- # private
- # def writers_and_readers
- # logged_in? ? "writer_layout" : "reader_layout"
- # end
- #
- # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
- # is logged in or not.
- #
- # If you want to use an inline method, such as a proc, do something like this:
- #
- # class WeblogController < ActionController::Base
- # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
- #
- # Of course, the most common way of specifying a layout is still just as a plain template name:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard"
- #
- # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
- # Otherwise, it will be looked up relative to the template root.
- #
- # == Conditional layouts
- #
- # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
- # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
- # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard", :except => :rss
- #
- # # ...
- #
- # end
- #
- # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
- # around the rendered view.
- #
- # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
- # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
- #
- # == Using a different layout in the action render call
- #
- # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
- # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
- # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard"
- #
- # def help
- # render :action => "help", :layout => "help"
- # end
- # end
- #
- # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
- module Layouts
- extend ActiveSupport::Concern
-
- include ActionController::Rendering
- include AbstractController::Layouts
-
- module ClassMethods
- # If no layout is provided, look for a layout with this name.
- def _implied_layout_name
- controller_path
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/logger.rb b/actionpack/lib/action_controller/metal/logger.rb
index e71f77fbb2..4f4370e5f0 100644
--- a/actionpack/lib/action_controller/metal/logger.rb
+++ b/actionpack/lib/action_controller/metal/logger.rb
@@ -8,6 +8,10 @@ module ActionController
module Logger
extend ActiveSupport::Concern
+ included do
+ include AbstractController::Logger
+ end
+
attr_internal :view_runtime
def process_action(action)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 20eb524e50..74e50bb032 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -20,12 +20,6 @@ module ActionController
def render_to_body(options)
_process_options(options)
-
- if options.key?(:partial)
- options[:partial] = action_name if options[:partial] == true
- options[:_details] = {:formats => formats}
- end
-
super
end
@@ -43,6 +37,12 @@ module ActionController
super
end
+ def _render_partial(options)
+ options[:partial] = action_name if options[:partial] == true
+ options[:_details] = {:formats => formats}
+ super
+ end
+
def format_for_text
formats.first
end
diff --git a/actionpack/lib/action_controller/rails.rb b/actionpack/lib/action_controller/railtie.rb
index df708c315b..f861d12905 100644
--- a/actionpack/lib/action_controller/rails.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -1,5 +1,8 @@
+require "action_controller"
+require "rails"
+
module ActionController
- class Plugin < Rails::Plugin
+ class Railtie < Rails::Railtie
plugin_name :action_controller
initializer "action_controller.set_configs" do |app|
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index d0c66eda60..81c9c88820 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -504,8 +504,9 @@ module ActionView
end
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
- # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
- # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
+ # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
+ # is found in the current I18n locale (through views.labels.<modelname>.<attribute>) or you specify it explicitly.
+ # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
# target labels for radio_button tags (where the value is used in the ID of the input tag).
#
@@ -513,6 +514,29 @@ module ActionView
# label(:post, :title)
# # => <label for="post_title">Title</label>
#
+ # You can localize your labels based on model and attribute names.
+ # For example you can define the following in your locale (e.g. en.yml)
+ #
+ # views:
+ # labels:
+ # post:
+ # body: "Write your entire text here"
+ #
+ # Which then will result in
+ #
+ # label(:post, :body)
+ # # => <label for="post_body">Write your entire text here</label>
+ #
+ # Localization can also be based purely on the translation of the attribute-name like this:
+ #
+ # activemodel:
+ # attribute:
+ # post:
+ # cost: "Total cost"
+ #
+ # label(:post, :cost)
+ # # => <label for="post_cost">Total cost</label>
+ #
# label(:post, :title, "A short title")
# # => <label for="post_title">A short title</label>
#
@@ -751,7 +775,19 @@ module ActionView
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options["for"] ||= name_and_id["id"]
- content = (text.blank? ? nil : text.to_s) || method_name.humanize
+
+ content = if text.blank?
+ I18n.t("views.labels.#{object_name}.#{method_name}", :default => "").presence
+ else
+ text.to_s
+ end
+
+ content ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(method_name)
+ end
+
+ content ||= method_name.humanize
+
label_tag(name_and_id["id"], content, options)
end
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
new file mode 100644
index 0000000000..a90e0636b9
--- /dev/null
+++ b/actionpack/lib/action_view/railtie.rb
@@ -0,0 +1,2 @@
+require "action_view"
+require "rails" \ No newline at end of file
diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb
index 0302e44b4e..48316cac53 100644
--- a/actionpack/lib/action_view/render/rendering.rb
+++ b/actionpack/lib/action_view/render/rendering.rb
@@ -22,15 +22,18 @@ module ActionView
return _render_partial(options)
end
- layout = find(layout, {:formats => formats}) if layout
+ template = if options[:file]
+ find(options[:file], {:formats => formats})
+ elsif options[:inline]
+ handler = Template.handler_class_for_extension(options[:type] || "erb")
+ Template.new(options[:inline], "inline template", handler, {})
+ elsif options[:text]
+ Template::Text.new(options[:text])
+ end
- if file = options[:file]
- template = find(file, {:formats => formats})
+ if template
+ layout = find(layout, {:formats => formats}) if layout
_render_template(template, layout, :locals => options[:locals])
- elsif inline = options[:inline]
- _render_inline(inline, layout, options)
- elsif text = options[:text]
- _render_text(text, layout, options[:locals])
end
when :update
update_page(&block)
@@ -76,42 +79,23 @@ module ActionView
capture(&block)
end
- def _render_inline(inline, layout, options)
- locals = options[:locals]
-
- content = ActiveSupport::Notifications.instrument(:render_inline) do
- handler = Template.handler_class_for_extension(options[:type] || "erb")
- template = Template.new(options[:inline], "inline template", handler, {})
- template.render(self, locals)
- end
-
- _render_text(content, layout, locals)
- end
-
- def _render_text(content, layout, locals)
- ActiveSupport::Notifications.instrument(:render_text)
- content = _render_layout(layout, locals){ content } if layout
- content
- end
-
# This is the API to render a ViewContext's template from a controller.
#
# Internal Options:
# _template:: The Template object to render
# _layout:: The layout, if any, to wrap the Template in
- # _partial:: true if the template is a partial
def render_template(options)
_evaluate_assigns_and_ivars
- template, layout, partial = options.values_at(:_template, :_layout, :_partial)
- _render_template(template, layout, options, partial)
+ template, layout = options.values_at(:_template, :_layout)
+ _render_template(template, layout, options)
end
- def _render_template(template, layout = nil, options = {}, partial = nil)
+ def _render_template(template, layout = nil, options = {})
locals = options[:locals] || {}
content = ActiveSupport::Notifications.instrument(:render_template,
:identifier => template.identifier, :layout => (layout ? layout.identifier : nil)) do
- partial ? _render_partial_object(template, options) : template.render(self, locals)
+ template.render(self, locals)
end
@_content_for[:layout] = content
diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb
index 84d5ce6ad4..c8e8b3857e 100644
--- a/actionpack/test/controller/cookie_test.rb
+++ b/actionpack/test/controller/cookie_test.rb
@@ -155,7 +155,7 @@ class CookieTest < ActionController::TestCase
def test_permanent_cookie
get :set_permanent_cookie
assert_match /Jamie/, @response.headers["Set-Cookie"]
- assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"]
+ assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"]
end
def test_signed_cookie
@@ -165,7 +165,7 @@ class CookieTest < ActionController::TestCase
def test_permanent_signed_cookie
get :set_permanent_signed_cookie
- assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"]
+ assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"]
assert_equal 100, @controller.send(:cookies).signed[:remember_me]
end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index 823de8bdc7..b0e5d7a94c 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -54,6 +54,7 @@ end
class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
+ extend ActiveModel::Translation
alias_method :secret?, :secret
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 44734abb18..b1e9fe99a2 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -6,6 +6,25 @@ class FormHelperTest < ActionView::TestCase
def setup
super
+
+ # Create "label" locale for testing I18n label helpers
+ I18n.backend.store_translations 'label', {
+ :activemodel => {
+ :attributes => {
+ :post => {
+ :cost => "Total cost"
+ }
+ }
+ },
+ :views => {
+ :labels => {
+ :post => {
+ :body => "Write entire text here"
+ }
+ }
+ }
+ }
+
@post = Post.new
@comment = Comment.new
def @post.errors()
@@ -51,6 +70,27 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<label for="post_secret">Secret?</label>', label(:post, :secret?))
end
+ def test_label_with_locales_strings
+ old_locale, I18n.locale = I18n.locale, :label
+ assert_dom_equal('<label for="post_body">Write entire text here</label>', label("post", "body"))
+ ensure
+ I18n.locale = old_locale
+ end
+
+ def test_label_with_human_attribute_name
+ old_locale, I18n.locale = I18n.locale, :label
+ assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost))
+ ensure
+ I18n.locale = old_locale
+ end
+
+ def test_label_with_locales_symbols
+ old_locale, I18n.locale = I18n.locale, :label
+ assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body))
+ ensure
+ I18n.locale = old_locale
+ end
+
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 8f855958c6..977a101277 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -235,10 +235,6 @@ module ActiveModel
# It's also possible to instantiate related objects, so a Client class belonging to the clients
# table with a +master_id+ foreign key can instantiate master through Client#master.
def method_missing(method_id, *args, &block)
- if method_id == :to_ary || method_id == :to_str
- raise NoMethodError, "undefined method `#{method_id}' for #{inspect}:#{self.class}"
- end
-
method_name = method_id.to_s
if match = match_attribute_method?(method_name)
guard_private_attribute_method!(method_name, args)
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index e8bb62953d..abc084a74b 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -97,18 +97,19 @@ module ActiveModel
full_messages = []
each do |attribute, messages|
- messages = Array.wrap(messages)
+ messages = Array(messages)
next if messages.empty?
if attribute == :base
messages.each {|m| full_messages << m }
else
- attr_name = @base.class.human_attribute_name(attribute)
- options = { :default => ' ', :scope => @base.class.i18n_scope }
- prefix = attr_name + I18n.t(:"errors.format.separator", options)
+ attr_name = attribute.to_s.gsub('.', '_').humanize
+ attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
+ options = { :default => "{{attribute}} {{message}}", :attribute => attr_name,
+ :scope => @base.class.i18n_scope }
messages.each do |m|
- full_messages << "#{prefix}#{m}"
+ full_messages << I18n.t(:"errors.format", options.merge(:message => m))
end
end
end
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 1c2347adbf..0be82aa180 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -41,6 +41,20 @@ module ActiveModel
assert_boolean model.destroyed?, "destroyed?"
end
+ # naming
+ # ------
+ #
+ # Model.model_name must returns a string with some convenience methods as
+ # :human and :partial_path. Check ActiveModel::Naming for more information.
+ #
+ def test_model_naming
+ assert model.class.respond_to?(:model_name), "The model should respond to model_name"
+ model_name = model.class.model_name
+ assert_kind_of String, model_name
+ assert_kind_of String, model_name.human
+ assert_kind_of String, model_name.partial_path
+ end
+
# errors
# ------
#
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index 0c2cf9ea33..1cdb897f13 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -1,6 +1,9 @@
en:
activemodel:
errors:
+ # model.errors.full_messages format.
+ format: "{{attribute}} {{message}}"
+
# The values :model, :attribute and :value are always available for interpolation
# The value :count is available when applicable. Can be used for pluralization.
messages:
diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb
new file mode 100644
index 0000000000..63ffe5db63
--- /dev/null
+++ b/activemodel/lib/active_model/railtie.rb
@@ -0,0 +1,2 @@
+require "active_model"
+require "rails" \ No newline at end of file
diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb
index da7d2112dc..63804004ee 100644
--- a/activemodel/test/cases/lint_test.rb
+++ b/activemodel/test/cases/lint_test.rb
@@ -4,6 +4,8 @@ class LintTest < ActiveModel::TestCase
include ActiveModel::Lint::Tests
class CompliantModel
+ extend ActiveModel::Naming
+
def to_model
self
end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 2717a09331..a7656fe219 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -21,20 +21,6 @@ class I18nValidationTest < ActiveModel::TestCase
I18n.backend = @old_backend
end
- def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated
- assert_not_deprecated do
- default = "%s interpolation syntax was deprecated"
- assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this')
- end
- end
-
- def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated
- assert_not_deprecated do
- default = "%d interpolation syntaxes are deprecated"
- assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2)
- end
- end
-
def test_errors_add_on_empty_generates_message
@person.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
@person.errors.add_on_empty :title
@@ -57,10 +43,16 @@ class I18nValidationTest < ActiveModel::TestCase
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@person.errors.add('name', 'empty')
- I18n.expects(:translate).with(:"person.name", :default => ['Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name')
+ I18n.expects(:translate).with(:"person.name", :default => ['Name', 'Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name')
@person.errors.full_messages
end
+ def test_errors_full_messages_uses_format
+ I18n.backend.store_translations('en', :activemodel => {:errors => {:format => "Field {{attribute}} {{message}}"}})
+ @person.errors.add('name', 'empty')
+ assert_equal ["Field Name empty"], @person.errors.full_messages
+ end
+
# ActiveRecord::Validations
# validates_confirmation_of w/ mocha
def test_validates_confirmation_of_generates_message
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 61910395b5..38a2a716a7 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -71,6 +71,12 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal 2, r.errors.count
end
+ def test_errors_on_nested_attributes_expands_name
+ t = Topic.new
+ t.errors["replies.name"] << "can't be blank"
+ assert_equal ["Replies name can't be blank"], t.errors.full_messages
+ end
+
def test_errors_on_base
r = Reply.new
r.content = "Mismatch"
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index a524dc50a1..cf439b0dc0 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -54,6 +54,7 @@ module ActiveRecord
autoload :QueryMethods
autoload :FinderMethods
autoload :CalculationMethods
+ autoload :PredicateBuilder
end
autoload :Base
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f0bad6c3ba..d74b21b690 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1465,8 +1465,7 @@ module ActiveRecord
after_destroy(method_name)
end
- def find_with_associations(options = {}, join_dependency = nil)
- join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
+ def find_with_associations(options = {}, join_dependency)
rows = select_all_rows(options, join_dependency)
join_dependency.instantiate(rows)
rescue ThrowResult
@@ -1488,7 +1487,7 @@ module ActiveRecord
dependent_conditions = []
dependent_conditions << "#{reflection.primary_key_name} = \#{record.#{reflection.name}.send(:owner_quoted_id)}"
dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
- dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name) if reflection.options[:conditions]
+ dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.table_name) if reflection.options[:conditions]
dependent_conditions << extra_conditions if extra_conditions
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
dependent_conditions = dependent_conditions.gsub('@', '\@')
@@ -1706,7 +1705,7 @@ module ActiveRecord
def construct_finder_arel_with_included_associations(options, join_dependency)
scope = scope(:find)
- relation = arel_table
+ relation = active_relation
for association in join_dependency.join_associations
relation = association.join_relation(relation)
@@ -1751,7 +1750,7 @@ module ActiveRecord
def construct_finder_sql_for_association_limiting(options, join_dependency)
scope = scope(:find)
- relation = arel_table(options[:from])
+ relation = active_relation
for association in join_dependency.join_associations
relation = association.join_relation(relation)
@@ -1764,89 +1763,12 @@ module ActiveRecord
order(construct_order(options[:order], scope)).
limit(construct_limit(options[:limit], scope)).
offset(construct_limit(options[:offset], scope)).
+ from(options[:from]).
select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(",")))
relation.to_sql
end
- def tables_in_string(string)
- return [] if string.blank?
- string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten
- end
-
- def tables_in_hash(hash)
- return [] if hash.blank?
- tables = hash.map do |key, value|
- if value.is_a?(Hash)
- key.to_s
- else
- tables_in_string(key) if key.is_a?(String)
- end
- end
- tables.flatten.compact
- end
-
- def conditions_tables(options)
- # look in both sets of conditions
- conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
- case cond
- when nil then all
- when Array then all << tables_in_string(cond.first)
- when Hash then all << tables_in_hash(cond)
- else all << tables_in_string(cond)
- end
- end
- conditions.flatten
- end
-
- def order_tables(options)
- order = [options[:order], scope(:find, :order) ].join(", ")
- return [] unless order && order.is_a?(String)
- tables_in_string(order)
- end
-
- def selects_tables(options)
- select = options[:select]
- return [] unless select && select.is_a?(String)
- tables_in_string(select)
- end
-
- def joined_tables(options)
- scope = scope(:find)
- joins = options[:joins]
- merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
- [table_name] + case merged_joins
- when Symbol, Hash, Array
- if array_of_strings?(merged_joins)
- tables_in_string(merged_joins.join(' '))
- else
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_joins, nil)
- join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
- end
- else
- tables_in_string(merged_joins)
- end
- end
-
- # Checks if the conditions reference a table other than the current model table
- def include_eager_conditions?(options, tables = nil, joined_tables = nil)
- ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
- end
-
- # Checks if the query order references a table other than the current model's table.
- def include_eager_order?(options, tables = nil, joined_tables = nil)
- ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
- end
-
- def include_eager_select?(options, joined_tables = nil)
- (selects_tables(options) - (joined_tables || joined_tables(options))).any?
- end
-
- def references_eager_loaded_tables?(options)
- joined_tables = joined_tables(options)
- include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
- end
-
def using_limitable_reflections?(reflections)
reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 1ceb0dbf96..358db6df1d 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -21,7 +21,7 @@ module ActiveRecord
construct_sql
end
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped
+ delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
def select(select = nil, &block)
if block_given?
@@ -58,7 +58,7 @@ module ActiveRecord
find_scope = construct_scope[:find].slice(:conditions, :order)
with_scope(:find => find_scope) do
- relation = @reflection.klass.send(:construct_finder_arel_with_includes, options)
+ relation = @reflection.klass.send(:construct_finder_arel, options)
case args.first
when :first, :last, :all
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 7d8f4670fa..022dd2ae9b 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -161,7 +161,7 @@ module ActiveRecord
end
# Forwards the call to the reflection class.
- def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name)
+ def sanitize_sql(sql, table_name = @reflection.klass.table_name)
@reflection.klass.send(:sanitize_sql, sql, table_name)
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 9569b0c6f9..bd05d1014c 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -44,7 +44,7 @@ module ActiveRecord
if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
- relation = arel_table(@reflection.options[:join_table])
+ relation = Arel::Table.new(@reflection.options[:join_table])
attributes = columns.inject({}) do |attrs, column|
case column.name.to_s
when @reflection.primary_key_name.to_s
@@ -70,7 +70,7 @@ module ActiveRecord
if sql = @reflection.options[:delete_sql]
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
else
- relation = arel_table(@reflection.options[:join_table])
+ relation = Arel::Table.new(@reflection.options[:join_table])
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id)))
).delete
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index be74ddfcf0..d3336cf2d2 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -69,7 +69,7 @@ module ActiveRecord
when :delete_all
@reflection.klass.delete(records.map { |record| record.id })
else
- relation = arel_table(@reflection.table_name)
+ relation = Arel::Table.new(@reflection.table_name)
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id)))
).update(relation[@reflection.primary_key_name] => nil)
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 44c668b619..98ab64537e 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -267,7 +267,7 @@ module ActiveRecord
unless valid = association.valid?
if reflection.options[:autosave]
association.errors.each do |attribute, message|
- attribute = "#{reflection.name}_#{attribute}"
+ attribute = "#{reflection.name}.#{attribute}"
errors[attribute] << message if errors[attribute].empty?
end
else
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c4bdbdad08..70776c7aa2 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -645,7 +645,7 @@ module ActiveRecord #:nodoc:
options = args.extract_options!
set_readonly_option!(options)
- relation = construct_finder_arel_with_includes(options)
+ relation = construct_finder_arel(options)
case args.first
when :first, :last, :all
@@ -655,7 +655,7 @@ module ActiveRecord #:nodoc:
end
end
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped
+ delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
# A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
# same arguments to this method as you can to <tt>find(:first)</tt>.
@@ -815,8 +815,8 @@ module ActiveRecord #:nodoc:
#
# # Delete multiple rows
# Todo.delete([2,3,4])
- def delete(id)
- delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
+ def delete(id_or_array)
+ active_relation.where(construct_conditions(nil, scope(:find))).delete(id_or_array)
end
# Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
@@ -873,7 +873,7 @@ module ActiveRecord #:nodoc:
def update_all(updates, conditions = nil, options = {})
scope = scope(:find)
- relation = arel_table
+ relation = active_relation
if conditions = construct_conditions(conditions, scope)
relation = relation.where(Arel::SqlLiteral.new(conditions))
@@ -938,7 +938,7 @@ module ActiveRecord #:nodoc:
# Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
- arel_table.where(construct_conditions(conditions, scope(:find))).delete_all
+ active_relation.where(construct_conditions(conditions, scope(:find))).delete_all
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
@@ -1391,7 +1391,8 @@ module ActiveRecord #:nodoc:
# end
def reset_column_information
undefine_attribute_methods
- @arel_table = @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
+ @active_relation = @active_relation_engine = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -1504,8 +1505,16 @@ module ActiveRecord #:nodoc:
"(#{segments.join(') AND (')})" unless segments.empty?
end
- def arel_table(table = nil)
- Relation.new(self, Arel::Table.new(table || table_name))
+ def active_relation
+ @active_relation ||= Relation.new(self, active_relation_table)
+ end
+
+ def active_relation_table(table_name_alias = nil)
+ Arel::Table.new(table_name, :as => table_name_alias)
+ end
+
+ def active_relation_engine
+ @active_relation_engine ||= Arel::Sql::Engine.new(self)
end
private
@@ -1561,7 +1570,7 @@ module ActiveRecord #:nodoc:
def construct_finder_arel(options = {}, scope = scope(:find))
validate_find_options(options)
- relation = arel_table.
+ relation = active_relation.
joins(construct_join(options[:joins], scope)).
where(construct_conditions(options[:conditions], scope)).
select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))).
@@ -1570,7 +1579,8 @@ module ActiveRecord #:nodoc:
order(construct_order(options[:order], scope)).
limit(construct_limit(options[:limit], scope)).
offset(construct_offset(options[:offset], scope)).
- from(options[:from])
+ from(options[:from]).
+ includes( merge_includes(scope && scope[:include], options[:include]))
lock = (scope && scope[:lock]) || options[:lock]
relation = relation.lock if lock.present?
@@ -1580,21 +1590,6 @@ module ActiveRecord #:nodoc:
relation
end
- def construct_finder_arel_with_includes(options = {})
- relation = construct_finder_arel(options)
- include_associations = merge_includes(scope(:find, :include), options[:include])
-
- if include_associations.any?
- if references_eager_loaded_tables?(options)
- relation = relation.eager_load(include_associations)
- else
- relation = relation.preload(include_associations)
- end
- end
-
- relation
- end
-
def construct_join(joins, scope)
merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
case merged_joins
@@ -1662,7 +1657,7 @@ module ActiveRecord #:nodoc:
def build_association_joins(joins)
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil)
- relation = arel_table.relation
+ relation = active_relation.relation
join_dependency.join_associations.map { |association|
if (association_relation = association.relation).is_a?(Array)
[Arel::InnerJoin.new(relation, association_relation.first, association.association_join.first).joins(relation),
@@ -1677,14 +1672,14 @@ module ActiveRecord #:nodoc:
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
end
- def type_condition(table_alias=nil)
- quoted_table_alias = self.connection.quote_table_name(table_alias || table_name)
- quoted_inheritance_column = connection.quote_column_name(inheritance_column)
- type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' " ) do |condition, subclass|
- condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
- end
+ def type_condition(table_alias = nil)
+ table = Arel::Table.new(table_name, :engine => active_relation_engine, :as => table_alias)
+
+ sti_column = table[inheritance_column]
+ condition = sti_column.eq(sti_name)
+ subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
- " (#{type_condition}) "
+ condition.to_sql
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -1713,7 +1708,7 @@ module ActiveRecord #:nodoc:
super unless all_attributes_exists?(attribute_names)
if match.finder?
options = arguments.extract_options!
- relation = options.any? ? construct_finder_arel_with_includes(options) : scoped
+ relation = options.any? ? construct_finder_arel(options) : scoped
relation.send :find_by_attributes, match, attribute_names, *arguments
elsif match.instantiator?
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
@@ -1964,7 +1959,7 @@ module ActiveRecord #:nodoc:
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition, table_name = quoted_table_name)
+ def sanitize_sql_for_conditions(condition, table_name = self.table_name)
return nil if condition.blank?
case condition
@@ -2035,30 +2030,12 @@ module ActiveRecord #:nodoc:
# And for value objects on a composed_of relationship:
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
- def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name)
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
- conditions = attrs.map do |attr, value|
- table_name = default_table_name
-
- unless value.is_a?(Hash)
- attr = attr.to_s
-
- # Extract table name from qualified attribute names.
- if attr.include?('.')
- attr_table_name, attr = attr.split('.', 2)
- attr_table_name = connection.quote_table_name(attr_table_name)
- else
- attr_table_name = table_name
- end
-
- attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
- else
- sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
- end
- end.join(' AND ')
-
- replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
+ table = Arel::Table.new(default_table_name, active_relation_engine)
+ builder = PredicateBuilder.new(active_relation_engine)
+ builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ')
end
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -2323,7 +2300,7 @@ module ActiveRecord #:nodoc:
# be made (since they can't be persisted).
def destroy
unless new_record?
- self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete
+ self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).delete_all
end
@destroyed = true
@@ -2610,7 +2587,7 @@ module ActiveRecord #:nodoc:
def update(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return 0 if attributes_with_values.empty?
- self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
+ self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).update(attributes_with_values)
end
# Creates a record with values matching those of the instance attributes
@@ -2623,9 +2600,9 @@ module ActiveRecord #:nodoc:
attributes_values = arel_attributes_values
new_id = if attributes_values.empty?
- self.class.arel_table.insert connection.empty_insert_statement_value
+ self.class.active_relation.insert connection.empty_insert_statement_value
else
- self.class.arel_table.insert attributes_values
+ self.class.active_relation.insert attributes_values
end
self.id ||= new_id
@@ -2720,7 +2697,7 @@ module ActiveRecord #:nodoc:
if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array))
value = value.to_yaml
end
- attrs[self.class.arel_table[name]] = value
+ attrs[self.class.active_relation[name]] = value
end
end
end
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index d51d9f2159..20d287faeb 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -162,7 +162,7 @@ module ActiveRecord
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(options[:joins], scope))
construct_calculation_arel_with_included_associations(options, join_dependency)
else
- arel_table.
+ active_relation.
joins(construct_join(options[:joins], scope)).
from((scope && scope[:from]) || options[:from]).
where(construct_conditions(options[:conditions], scope)).
@@ -178,7 +178,7 @@ module ActiveRecord
def construct_calculation_arel_with_included_associations(options, join_dependency)
scope = scope(:find)
- relation = arel_table
+ relation = active_relation
for association in join_dependency.join_associations
relation = association.join_relation(relation)
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 092f5f0023..e33d389f8c 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -1,6 +1,9 @@
en:
activerecord:
errors:
+ # model.errors.full_messages format.
+ format: "{{attribute}} {{message}}"
+
# The values :model, :attribute and :value are always available for interpolation
# The value :count is available when applicable. Can be used for pluralization.
messages:
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 986bc7009b..f9e538c586 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -78,11 +78,11 @@ module ActiveRecord
attribute_names.uniq!
begin
- arel_table = self.class.arel_table(self.class.table_name)
+ relation = self.class.active_relation
- affected_rows = arel_table.where(
- arel_table[self.class.primary_key].eq(quoted_id).and(
- arel_table[self.class.locking_column].eq(quote_value(previous_value))
+ affected_rows = relation.where(
+ relation[self.class.primary_key].eq(quoted_id).and(
+ relation[self.class.locking_column].eq(quote_value(previous_value))
)
).update(arel_attributes_values(false, false, attribute_names))
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index a6336e762a..f63b249241 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -27,9 +27,9 @@ module ActiveRecord
Scope.new(self, options, &block)
else
unless scoped?(:find)
- finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table
+ finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn
else
- construct_finder_arel_with_includes
+ construct_finder_arel
end
end
end
diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/railtie.rb
index a13bd2a5da..657ee738c0 100644
--- a/activerecord/lib/active_record/rails.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -2,10 +2,12 @@
# rails, so let's make sure that it gets required before
# here. This is needed for correctly setting up the middleware.
# In the future, this might become an optional require.
-require "action_controller/rails"
+require "active_record"
+require "action_controller/railtie"
+require "rails"
module ActiveRecord
- class Plugin < Rails::Plugin
+ class Railtie < Rails::Railtie
plugin_name :active_record
rake_tasks do
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ae03e1d7e9..114095b7ef 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -2,32 +2,60 @@ module ActiveRecord
class Relation
include QueryMethods, FinderMethods, CalculationMethods
- delegate :to_sql, :to => :relation
delegate :length, :collect, :map, :each, :all?, :to => :to_a
- attr_reader :relation, :klass, :preload_associations, :eager_load_associations
- attr_writer :readonly, :preload_associations, :eager_load_associations
+ attr_reader :relation, :klass
+ attr_writer :readonly, :table
+ attr_accessor :preload_associations, :eager_load_associations, :include_associations
def initialize(klass, relation)
@klass, @relation = klass, relation
@preload_associations = []
@eager_load_associations = []
+ @include_associations = []
@loaded, @readonly = false
end
+ def new(*args, &block)
+ with_create_scope { @klass.new(*args, &block) }
+ end
+
+ def create(*args, &block)
+ with_create_scope { @klass.create(*args, &block) }
+ end
+
+ def create!(*args, &block)
+ with_create_scope { @klass.create!(*args, &block) }
+ end
+
def merge(r)
raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
- joins(r.relation.joins(r.relation)).
- group(r.send(:group_clauses).join(', ')).
- order(r.send(:order_clauses).join(', ')).
- where(r.send(:where_clause)).
- limit(r.taken).
- offset(r.skipped).
- select(r.send(:select_clauses).join(', ')).
- eager_load(r.eager_load_associations).
- preload(r.preload_associations).
- from(r.send(:sources).present? ? r.send(:from_clauses) : nil)
+ merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations)
+ merged_relation.readonly = r.readonly
+
+ [self.relation, r.relation].each do |arel|
+ merged_relation = merged_relation.
+ joins(arel.joins(arel)).
+ group(arel.groupings).
+ order(arel.send(:order_clauses).join(', ')).
+ limit(arel.taken).
+ offset(arel.skipped).
+ select(arel.send(:select_clauses)).
+ from(arel.sources)
+ end
+
+ merged_wheres = @relation.wheres
+
+ r.wheres.each do |w|
+ if w.is_a?(Arel::Predicates::Equality)
+ merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
+ end
+
+ merged_wheres << w
+ end
+
+ merged_relation.where(*merged_wheres)
end
alias :& :merge
@@ -47,7 +75,9 @@ module ActiveRecord
def to_a
return @records if loaded?
- @records = if @eager_load_associations.any?
+ find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables?
+
+ @records = if find_with_associations
begin
@klass.send(:find_with_associations, {
:select => @relation.send(:select_clauses).join(', '),
@@ -59,7 +89,7 @@ module ActiveRecord
:offset => @relation.skipped,
:from => (@relation.send(:from_clauses) if @relation.send(:sources).present?)
},
- ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil))
+ ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @include_associations, nil))
rescue ThrowResult
[]
end
@@ -67,7 +97,10 @@ module ActiveRecord
@klass.find_by_sql(@relation.to_sql)
end
- @preload_associations.each {|associations| @klass.send(:preload_associations, @records, associations) }
+ preload = @preload_associations
+ preload += @include_associations unless find_with_associations
+ preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
+
@records.each { |record| record.readonly! } if @readonly
@loaded = true
@@ -109,6 +142,10 @@ module ActiveRecord
@relation.delete.tap { reset }
end
+ def delete(id_or_array)
+ where(@klass.primary_key => id_or_array).delete_all
+ end
+
def loaded?
@loaded
end
@@ -119,19 +156,33 @@ module ActiveRecord
end
def reset
- @first = @last = nil
+ @first = @last = @create_scope = @to_sql = nil
@records = []
self
end
def spawn(relation = @relation)
- relation = self.class.new(@klass, relation)
+ relation = Relation.new(@klass, relation)
relation.readonly = @readonly
relation.preload_associations = @preload_associations
relation.eager_load_associations = @eager_load_associations
+ relation.include_associations = @include_associations
+ relation.table = table
relation
end
+ def table
+ @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass))
+ end
+
+ def primary_key
+ @primary_key ||= table[@klass.primary_key]
+ end
+
+ def to_sql
+ @to_sql ||= @relation.to_sql
+ end
+
protected
def method_missing(method, *args, &block)
@@ -153,9 +204,30 @@ module ActiveRecord
end
end
- def where_clause(join_string = "\n\tAND ")
+ def with_create_scope
+ @klass.send(:with_scope, :create => create_scope) { yield }
+ end
+
+ def create_scope
+ @create_scope ||= wheres.inject({}) do |hash, where|
+ hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
+ hash
+ end
+ end
+
+ def where_clause(join_string = " AND ")
@relation.send(:where_clauses).join(join_string)
end
+ def references_eager_loaded_tables?
+ joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq
+ (tables_in_string(to_sql) - joined_tables).any?
+ end
+
+ def tables_in_string(string)
+ return [] if string.blank?
+ string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq
+ end
+
end
end
diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb
index a925a99464..5246c7bc5d 100644
--- a/activerecord/lib/active_record/relation/calculation_methods.rb
+++ b/activerecord/lib/active_record/relation/calculation_methods.rb
@@ -53,7 +53,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
column = if @klass.column_names.include?(column_name.to_s)
- Arel::Attribute.new(@klass.arel_table, column_name)
+ Arel::Attribute.new(@klass.active_relation, column_name)
else
Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s)
end
@@ -77,7 +77,7 @@ module ActiveRecord
select_statement = if operation == 'count' && column_name == :all
"COUNT(*) AS count_all"
else
- Arel::Attribute.new(@klass.arel_table, column_name).send(operation).as(aggregate_alias).to_sql
+ Arel::Attribute.new(@klass.active_relation, column_name).send(operation).as(aggregate_alias).to_sql
end
select_statement << ", #{group_field} AS #{group_alias}"
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7a1d6fc538..c3e5f27838 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -21,8 +21,8 @@ module ActiveRecord
end
def exists?(id = nil)
- relation = select("#{@klass.quoted_table_name}.#{@klass.primary_key}").limit(1)
- relation = relation.where(@klass.primary_key => id) if id
+ relation = select(primary_key).limit(1)
+ relation = relation.where(primary_key.eq(id)) if id
relation.first ? true : false
end
@@ -78,7 +78,7 @@ module ActiveRecord
end
def find_one(id)
- record = where(@klass.primary_key => id).first
+ record = where(primary_key.eq(id)).first
unless record
conditions = where_clause(', ')
@@ -90,7 +90,7 @@ module ActiveRecord
end
def find_some(ids)
- result = where(@klass.primary_key => ids).all
+ result = where(primary_key.in(ids)).all
expected_size =
if @relation.taken && ids.size > @relation.taken
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
new file mode 100644
index 0000000000..6b7d941350
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -0,0 +1,45 @@
+module ActiveRecord
+ class PredicateBuilder
+
+ def initialize(engine)
+ @engine = engine
+ end
+
+ def build_from_hash(attributes, default_table)
+ predicates = attributes.map do |column, value|
+ table = default_table
+
+ if value.is_a?(Hash)
+ table = Arel::Table.new(column, :engine => @engine)
+ build_from_hash(value, table)
+ else
+ column = column.to_s
+
+ if column.include?('.')
+ table_name, column = column.split('.', 2)
+ table = Arel::Table.new(table_name, :engine => @engine)
+ end
+
+ attribute = table[column] || Arel::Attribute.new(table, column.to_sym)
+
+ case value
+ when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope
+ attribute.in(value)
+ when Range
+ # TODO : Arel should handle ranges with excluded end.
+ if value.exclude_end?
+ [attribute.gteq(value.begin), attribute.lt(value.end)]
+ else
+ attribute.in(value)
+ end
+ else
+ attribute.eq(value)
+ end
+ end
+ end
+
+ predicates.flatten
+ end
+
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 631c80da25..cf2cc7ba70 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,6 +5,10 @@ module ActiveRecord
spawn.tap {|r| r.preload_associations += Array.wrap(associations) }
end
+ def includes(*associations)
+ spawn.tap {|r| r.include_associations += Array.wrap(associations) }
+ end
+
def eager_load(*associations)
spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) }
end
@@ -104,14 +108,19 @@ module ActiveRecord
def where(*args)
return spawn if args.blank?
- if [String, Hash, Array].include?(args.first.class)
- conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
- conditions = Arel::SqlLiteral.new(conditions) if conditions
+ builder = PredicateBuilder.new(Arel::Sql::Engine.new(@klass))
+
+ conditions = if [String, Array].include?(args.first.class)
+ merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
+ Arel::SqlLiteral.new(merged) if merged
+ elsif args.first.is_a?(Hash)
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first)
+ builder.build_from_hash(attributes, table)
else
- conditions = args.first
+ args.first
end
- spawn(@relation.where(conditions))
+ conditions.is_a?(String) ? spawn(@relation.where(conditions)) : spawn(@relation.where(*conditions))
end
private
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index ffbe1b5c40..7efd312357 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -8,24 +8,25 @@ module ActiveRecord
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
+ table = finder_class.active_relation
+
table_name = record.class.quoted_table_name
sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
+ relation = table.where(sql, *params)
+
Array(options[:scope]).each do |scope_item|
scope_value = record.send(scope_item)
- sql << " AND " << record.class.send(:attribute_condition, "#{table_name}.#{scope_item}", scope_value)
- params << scope_value
+ relation = relation.where(scope_item => scope_value)
end
unless record.new_record?
- sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
- params << record.send(:id)
+ # TODO : This should be in Arel
+ relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
end
- finder_class.send(:with_exclusive_scope) do
- if finder_class.exists?([sql, *params])
- record.errors.add(attribute, :taken, :default => options[:message], :value => value)
- end
+ if relation.exists?
+ record.errors.add(attribute, :taken, :default => options[:message], :value => value)
end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 289c89d1e2..d359ad48c5 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -327,10 +327,4 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert !account.new_record?
assert_equal 500, account.credit_limit
end
-
- def test_create!_respects_hash_condition
- account = companies(:first_firm).create_account_limit_500_with_hash_conditions!
- assert !account.new_record?
- assert_equal 500, account.credit_limit
- end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 803e5b25b1..cf763d730a 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -786,14 +786,14 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@pirate.ship.name = ''
assert @pirate.invalid?
- assert @pirate.errors[:ship_name].any?
+ assert @pirate.errors[:"ship.name"].any?
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.ship.name = nil
@pirate.catchphrase = nil
assert @pirate.invalid?
- assert @pirate.errors[:ship_name].any?
+ assert @pirate.errors[:"ship.name"].any?
assert @pirate.errors[:catchphrase].any?
end
@@ -886,7 +886,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@ship.pirate.catchphrase = ''
assert @ship.invalid?
- assert @ship.errors[:pirate_catchphrase].any?
+ assert @ship.errors[:"pirate.catchphrase"].any?
end
def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
@@ -894,7 +894,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
@ship.pirate.catchphrase = nil
assert @ship.invalid?
assert @ship.errors[:name].any?
- assert @ship.errors[:pirate_catchphrase].any?
+ assert @ship.errors[:"pirate.catchphrase"].any?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@@ -961,7 +961,7 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).each { |child| child.name = '' }
assert !@pirate.valid?
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert @pirate.errors[@association_name].empty?
end
@@ -969,16 +969,33 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).build(:name => '')
assert !@pirate.valid?
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert @pirate.errors[@association_name].empty?
end
+ def test_should_default_invalid_error_from_i18n
+ I18n.backend.store_translations(:en, :activerecord => { :errors => { :models =>
+ { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } }
+ }})
+
+ @pirate.send(@association_name).build(:name => '')
+
+ assert !@pirate.valid?
+ assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"]
+ assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages
+ assert @pirate.errors[@association_name].empty?
+ ensure
+ I18n.backend.store_translations(:en, :activerecord => { :errors => { :models =>
+ { @association_name.to_s.singularize.to_sym => nil }
+ }})
+ end
+
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.send(@association_name).each { |child| child.name = '' }
@pirate.catchphrase = nil
assert !@pirate.valid?
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"]
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert @pirate.errors[:catchphrase].any?
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index ebb717812d..730d9d8df7 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -201,7 +201,7 @@ class BasicsTest < ActiveRecord::TestCase
topic = Topic.new(:title => "New Topic")
assert topic.save!
- reply = Reply.new
+ reply = WrongReply.new
assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
end
@@ -959,6 +959,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_update_attributes!
+ Reply.validates_presence_of(:title)
reply = Reply.find(2)
assert_equal "The Second Topic of the day", reply.title
assert_equal "Have a nice day", reply.content
@@ -974,6 +975,8 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "Have a nice day", reply.content
assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
+ ensure
+ Reply.reset_callbacks(:validate)
end
def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 60c5bad225..8891282915 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -592,7 +592,7 @@ module NestedAttributesOnACollectionAssociationTests
assert_no_difference ['Man.count', 'Interest.count'] do
man = Man.create(:name => 'John',
:interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
- assert !man.errors[:interests_man].empty?
+ assert !man.errors[:"interests.man"].empty?
end
end
# restore :inverse_of
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 6605c8bf34..18f6152cc0 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -10,6 +10,7 @@ require 'models/comment'
require 'models/entrant'
require 'models/developer'
require 'models/company'
+require 'models/bird'
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
@@ -177,7 +178,7 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_find_with_included_associations
+ def test_find_with_preloaded_associations
assert_queries(2) do
posts = Post.preload(:comments)
assert posts.first.comments.first
@@ -205,6 +206,29 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_find_with_included_associations
+ assert_queries(2) do
+ posts = Post.includes(:comments)
+ assert posts.first.comments.first
+ end
+
+ assert_queries(2) do
+ posts = Post.scoped.includes(:comments)
+ assert posts.first.comments.first
+ end
+
+ assert_queries(2) do
+ posts = Post.includes(:author)
+ assert posts.first.author
+ end
+
+ assert_queries(3) do
+ posts = Post.includes(:author, :comments).to_a
+ assert posts.first.author
+ assert posts.first.comments.first
+ end
+ end
+
def test_default_scope_with_conditions_string
assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
assert_equal nil, DeveloperCalledDavid.create!.name
@@ -477,4 +501,43 @@ class RelationTest < ActiveRecord::TestCase
assert posts.many?
assert ! posts.limit(1).many?
end
+
+ def test_build
+ posts = Post.scoped
+
+ post = posts.new
+ assert_kind_of Post, post
+ end
+
+ def test_scoped_build
+ posts = Post.where(:title => 'You told a lie')
+
+ post = posts.new
+ assert_kind_of Post, post
+ assert_equal 'You told a lie', post.title
+ end
+
+ def test_create
+ birds = Bird.scoped
+
+ sparrow = birds.create
+ assert_kind_of Bird, sparrow
+ assert sparrow.new_record?
+
+ hen = birds.where(:name => 'hen').create
+ assert ! hen.new_record?
+ assert_equal 'hen', hen.name
+ end
+
+ def test_create_bang
+ birds = Bird.scoped
+
+ assert_raises(ActiveRecord::RecordInvalid) { birds.create! }
+
+ hen = birds.where(:name => 'hen').create!
+ assert_kind_of Bird, hen
+ assert ! hen.new_record?
+ assert_equal 'hen', hen.name
+ end
+
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 278a7a6a06..5ed997356b 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -10,7 +10,7 @@ require 'models/interest'
class AssociationValidationTest < ActiveRecord::TestCase
fixtures :topics, :owners
- repair_validations(Topic)
+ repair_validations(Topic, Reply)
def test_validates_size_of_association
repair_validations(Owner) do
@@ -40,7 +40,8 @@ class AssociationValidationTest < ActiveRecord::TestCase
end
def test_validates_associated_many
- Topic.validates_associated( :replies )
+ Topic.validates_associated(:replies)
+ Reply.validates_presence_of(:content)
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")]
assert !t.valid?
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 532de67d99..f017f24048 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -3,8 +3,9 @@ require 'models/topic'
require 'models/reply'
class I18nValidationTest < ActiveRecord::TestCase
+ repair_validations(Topic, Reply)
def setup
- Topic.reset_callbacks(:validate)
+ Reply.validates_presence_of(:title)
@topic = Topic.new
@old_load_path, @old_backend = I18n.load_path, I18n.backend
I18n.load_path.clear
@@ -13,7 +14,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
def teardown
- Topic.reset_callbacks(:validate)
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index db633339f3..8f84841fe6 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -5,7 +5,6 @@ require 'models/reply'
require 'models/warehouse_thing'
require 'models/guid'
require 'models/event'
-require 'models/developer'
# The following methods in Topic are used in test_conditional_validation_*
class Topic
@@ -276,14 +275,4 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert w6.errors[:city].any?, "Should have errors for city"
assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city"
end
-
- def test_validates_uniqueness_of_with_custom_message_using_quotes
- repair_validations(Developer) do
- Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
- d = Developer.new
- d.name = "David"
- assert !d.valid?
- assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
- end
- end
end
diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb
index e04738d209..11912ca1cc 100644
--- a/activerecord/test/cases/validations_repair_helper.rb
+++ b/activerecord/test/cases/validations_repair_helper.rb
@@ -4,31 +4,19 @@ module ActiveRecord
module ClassMethods
def repair_validations(*model_classes)
- setup do
- @_stored_callbacks = {}
- model_classes.each do |k|
- @_stored_callbacks[k] = k._validate_callbacks.dup
- end
- end
teardown do
model_classes.each do |k|
- k._validate_callbacks = @_stored_callbacks[k]
- k.__update_callbacks(:validate)
+ k.reset_callbacks(:validate)
end
end
end
end
- def repair_validations(*model_classes, &block)
- @__stored_callbacks = {}
- model_classes.each do |k|
- @__stored_callbacks[k] = k._validate_callbacks.dup
- end
- return block.call
+ def repair_validations(*model_classes)
+ yield
ensure
model_classes.each do |k|
- k._validate_callbacks = @__stored_callbacks[k]
- k.__update_callbacks(:validate)
+ k.reset_callbacks(:validate)
end
end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 7462d944e0..3a1d5ae212 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -42,7 +42,7 @@ class ValidationsTest < ActiveRecord::TestCase
repair_validations(Topic)
def test_error_on_create
- r = Reply.new
+ r = WrongReply.new
r.title = "Wrong Create"
assert !r.valid?
assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
@@ -50,7 +50,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_error_on_update
- r = Reply.new
+ r = WrongReply.new
r.title = "Bad"
r.content = "Good"
assert r.save, "First save should be successful"
@@ -63,11 +63,11 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_invalid_record_exception
- assert_raise(ActiveRecord::RecordInvalid) { Reply.create! }
- assert_raise(ActiveRecord::RecordInvalid) { Reply.new.save! }
+ assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
+ assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! }
begin
- r = Reply.new
+ r = WrongReply.new
r.save!
flunk
rescue ActiveRecord::RecordInvalid => invalid
@@ -77,13 +77,13 @@ class ValidationsTest < ActiveRecord::TestCase
def test_exception_on_create_bang_many
assert_raise(ActiveRecord::RecordInvalid) do
- Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
+ WrongReply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
end
end
def test_exception_on_create_bang_with_block
assert_raise(ActiveRecord::RecordInvalid) do
- Reply.create!({ "title" => "OK" }) do |r|
+ WrongReply.create!({ "title" => "OK" }) do |r|
r.content = nil
end
end
@@ -91,15 +91,15 @@ class ValidationsTest < ActiveRecord::TestCase
def test_exception_on_create_bang_many_with_block
assert_raise(ActiveRecord::RecordInvalid) do
- Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
+ WrongReply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
r.content = nil
end
end
end
def test_scoped_create_without_attributes
- Reply.send(:with_scope, :create => {}) do
- assert_raise(ActiveRecord::RecordInvalid) { Reply.create! }
+ WrongReply.send(:with_scope, :create => {}) do
+ assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
end
end
@@ -122,15 +122,15 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_create_without_validation
- reply = Reply.new
+ reply = WrongReply.new
assert !reply.save
assert reply.save(false)
end
def test_create_without_validation_bang
- count = Reply.count
- assert_nothing_raised { Reply.new.save_without_validation! }
- assert count+1, Reply.count
+ count = WrongReply.count
+ assert_nothing_raised { WrongReply.new.save_without_validation! }
+ assert count+1, WrongReply.count
end
def test_validates_acceptance_of_with_non_existant_table
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index ba5a1d1d01..f1ba45b528 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -7,11 +7,13 @@ class Reply < Topic
belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count"
has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id"
+ attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title
+end
+
+class WrongReply < Reply
validate :errors_on_empty_content
validate :title_is_wrong_create, :on => :create
- attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title
-
validate :check_empty_title
validate :check_content_mismatch, :on => :create
validate :check_wrong_update, :on => :update
diff --git a/activeresource/lib/active_resource/railtie.rb b/activeresource/lib/active_resource/railtie.rb
new file mode 100644
index 0000000000..4f264c82b8
--- /dev/null
+++ b/activeresource/lib/active_resource/railtie.rb
@@ -0,0 +1,2 @@
+require "active_resource"
+require "rails" \ No newline at end of file
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 6a41aab166..ee02ecb043 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/benchmark'
+require 'active_support/core_ext/hash/keys'
module ActiveSupport
module Benchmarkable
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index f2d957f154..ad238c1d96 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/exception'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/object/to_param'
+require 'active_support/core_ext/string/inflections'
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
diff --git a/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb b/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb
index d87eb17337..d2370d78c5 100644
--- a/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb
@@ -1,3 +1,5 @@
+require 'active_support/gzip'
+
module ActiveSupport
module Cache
class CompressedMemCacheStore < MemCacheStore
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 1b6b820ca4..d584c9e254 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -1,4 +1,5 @@
require 'memcache'
+require 'active_support/core_ext/array/extract_options'
module ActiveSupport
module Cache
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 5f6fe22416..86c7703c27 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/string/inflections'
module ActiveSupport
module Cache
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 05bc453dbf..8719dc0e3f 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/object/metaclass'
module ActiveSupport
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
@@ -367,12 +368,6 @@ module ActiveSupport
method << "halted ? false : (block_given? ? value : true)"
method.compact.join("\n")
end
-
- def clone(klass)
- chain = CallbackChain.new(@name, @config.dup)
- callbacks = map { |c| c.clone(chain, klass) }
- chain.push(*callbacks)
- end
end
module ClassMethods
@@ -389,10 +384,16 @@ module ActiveSupport
# key. See #define_callbacks for more information.
#
def __define_runner(symbol) #:nodoc:
+ send("_update_#{symbol}_superclass_callbacks")
body = send("_#{symbol}_callbacks").compile(nil)
body, line = <<-RUBY_EVAL, __LINE__
def _run_#{symbol}_callbacks(key = nil, &blk)
+ if self.class.send("_update_#{symbol}_superclass_callbacks")
+ self.class.__define_runner(#{symbol.inspect})
+ return _run_#{symbol}_callbacks(key, &blk)
+ end
+
if key
name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
@@ -431,6 +432,8 @@ module ActiveSupport
# CallbackChain.
#
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
+ send("_update_#{name}_superclass_callbacks")
+
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
@@ -470,7 +473,8 @@ module ActiveSupport
def set_callback(name, *filter_list, &block)
__update_callbacks(name, filter_list, block) do |chain, type, filters, options|
filters.map! do |filter|
- chain.delete_if {|c| c.matches?(type, filter) }
+ removed = chain.delete_if {|c| c.matches?(type, filter) }
+ send("_removed_#{name}_callbacks").push(*removed)
Callback.new(chain, filter, type, options.dup, self)
end
@@ -482,16 +486,17 @@ module ActiveSupport
#
def skip_callback(name, *filter_list, &block)
__update_callbacks(name, filter_list, block) do |chain, type, filters, options|
- chain = send("_#{name}_callbacks=", chain.clone(self))
-
filters.each do |filter|
filter = chain.find {|c| c.matches?(type, filter) }
if filter && options.any?
- filter.recompile!(options, options[:per_key] || {})
- else
- chain.delete(filter)
+ new_filter = filter.clone(chain, self)
+ chain.insert(chain.index(filter), new_filter)
+ new_filter.recompile!(options, options[:per_key] || {})
end
+
+ chain.delete(filter)
+ send("_removed_#{name}_callbacks") << filter
end
end
end
@@ -499,7 +504,9 @@ module ActiveSupport
# Reset callbacks for a given type.
#
def reset_callbacks(symbol)
- send("_#{symbol}_callbacks").clear
+ callbacks = send("_#{symbol}_callbacks")
+ callbacks.clear
+ send("_removed_#{symbol}_callbacks").concat(callbacks)
__define_runner(symbol)
end
@@ -546,14 +553,46 @@ module ActiveSupport
#
# Defaults to :kind.
#
- def define_callbacks(*symbols)
- config = symbols.last.is_a?(Hash) ? symbols.pop : {}
- symbols.each do |symbol|
- extlib_inheritable_accessor("_#{symbol}_callbacks") do
- CallbackChain.new(symbol, config)
+ def define_callbacks(*callbacks)
+ config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
+ callbacks.each do |callback|
+ extlib_inheritable_reader("_#{callback}_callbacks") do
+ CallbackChain.new(callback, config)
+ end
+
+ extlib_inheritable_reader("_removed_#{callback}_callbacks") do
+ []
end
- __define_runner(symbol)
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def self._#{callback}_superclass_callbacks
+ if superclass.respond_to?(:_#{callback}_callbacks)
+ superclass._#{callback}_callbacks + superclass._#{callback}_superclass_callbacks
+ else
+ []
+ end
+ end
+
+ def self._update_#{callback}_superclass_callbacks
+ changed, index = false, 0
+
+ callbacks = (_#{callback}_superclass_callbacks -
+ _#{callback}_callbacks) - _removed_#{callback}_callbacks
+
+ callbacks.each do |callback|
+ if new_index = _#{callback}_callbacks.index(callback)
+ index = new_index + 1
+ else
+ changed = true
+ _#{callback}_callbacks.insert(index, callback)
+ index = index + 1
+ end
+ end
+ changed
+ end
+ METHOD
+
+ __define_runner(callback)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb b/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb
index 1c3ef05526..d3c3575748 100644
--- a/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb
+++ b/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb
@@ -1,3 +1,5 @@
+require 'cgi'
+
class CGI #:nodoc:
if RUBY_VERSION >= '1.9'
def self.escape_skipping_slashes(str)
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
index 301c09fc73..72e0eefb0a 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/array/extract_options'
class Class
diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
index e4d22516c1..2b8e2b544f 100644
--- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
@@ -159,7 +159,7 @@ class Class
# (error out or do the same as other methods above) instead of silently
# moving on). In particular, this makes the return value of this function
# less useful.
- def extlib_inheritable_reader(*ivars)
+ def extlib_inheritable_reader(*ivars, &block)
options = ivars.extract_options!
ivars.each do |ivar|
@@ -178,6 +178,7 @@ class Class
end
RUBY
end
+ instance_variable_set(:"@#{ivar}", yield) if block_given?
end
end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 2b76b93153..9d2ad2bbcf 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -1,6 +1,7 @@
require 'date'
require 'active_support/duration'
require 'active_support/core_ext/time/zones'
+require 'active_support/core_ext/object/acts_like'
class Date
class << self
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index e93abfa4a3..26979aa906 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -1,4 +1,5 @@
require 'rational' unless RUBY_VERSION >= '1.9.2'
+require 'active_support/core_ext/object/acts_like'
class DateTime
class << self
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 35ccec5df4..cfd840fb93 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -1,6 +1,8 @@
require 'active_support/time'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/reverse_merge'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/string/inflections'
class Hash
# This module exists to decorate files deserialized using Hash.from_xml with
diff --git a/activesupport/lib/active_support/core_ext/module/loading.rb b/activesupport/lib/active_support/core_ext/module/loading.rb
index 4b4b110b25..43d0578ae6 100644
--- a/activesupport/lib/active_support/core_ext/module/loading.rb
+++ b/activesupport/lib/active_support/core_ext/module/loading.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/inflections'
+
class Module
# Returns String#underscore applied to the module name minus trailing classes.
#
diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb
index f72d512340..115b8abd4e 100644
--- a/activesupport/lib/active_support/core_ext/module/synchronization.rb
+++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module/aliasing'
+require 'active_support/core_ext/array/extract_options'
class Module
# Synchronize access around a method, delegating synchronization to a
diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb
index de8121f274..b8b6970382 100644
--- a/activesupport/lib/active_support/core_ext/object/extending.rb
+++ b/activesupport/lib/active_support/core_ext/object/extending.rb
@@ -1,3 +1,6 @@
+require 'active_support/core_ext/class/removal'
+require 'active_support/core_ext/object/blank'
+
class Class
# Rubinius
if defined?(Class.__subclasses__)
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 7ca763cbad..49e41e919a 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -1,3 +1,5 @@
+
+
class Object
# Alias of <tt>to_s</tt>.
def to_param
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index deb29a82b8..cec8024b17 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/aliasing'
+require 'active_support/core_ext/array/extract_options'
module ActiveSupport
class << Deprecation
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 713ae1b671..c1f0e4bf81 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,5 +1,6 @@
require 'active_support/basic_object'
require 'active_support/core_ext/array/conversions'
+require 'active_support/core_ext/object/acts_like'
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 347af9dc76..51fa626b45 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -1,4 +1,5 @@
require 'openssl'
+require 'active_support/base64'
module ActiveSupport
# MessageEncryptor is a simple way to encrypt values which get stored somewhere
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 87e4b1ad33..6c46b68eaf 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -1,3 +1,6 @@
+require 'active_support/base64'
+require 'active_support/core_ext/object/blank'
+
module ActiveSupport
# MessageVerifier makes it easy to generate and verify messages which are signed
# to prevent tampering.
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index f0119f5994..6e660f8647 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -1,5 +1,7 @@
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/proc'
+require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# Rescuable module adds support for easier exception handling.
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 66e32fa5d7..24eea1e40b 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -4,6 +4,7 @@ begin
require 'fileutils'
require 'rails/version'
require 'active_support/core_ext/class/delegating_attributes'
+ require 'active_support/core_ext/string/inflections'
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 8304f6c434..6b554e7158 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,4 +1,5 @@
require "active_support/values/time_zone"
+require 'active_support/core_ext/object/acts_like'
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 5c3d93c4a1..48c1cb3fe9 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -3,6 +3,8 @@ raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFO
require 'jruby'
include Java
+require 'active_support/core_ext/object/blank'
+
import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory
import java.io.StringReader unless defined? StringReader
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 0f7ba1918b..9cf187302f 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -1,4 +1,6 @@
require 'libxml'
+require 'active_support/core_ext/object/returning'
+require 'active_support/core_ext/object/blank'
# = XmlMini LibXML implementation
module ActiveSupport
@@ -12,7 +14,7 @@ module ActiveSupport
if !data.respond_to?(:read)
data = StringIO.new(data || '')
end
-
+
char = data.getc
if char.nil?
{}
@@ -34,106 +36,42 @@ module LibXML #:nodoc:
end
module Node #:nodoc:
- CONTENT_ROOT = '__content__'
- LIB_XML_LIMIT = 30000000 # Hardcoded LibXML limit
+ CONTENT_ROOT = '__content__'.freeze
# Convert XML document to hash
#
# hash::
# Hash to merge the converted element into.
def to_hash(hash={})
- if text? || cdata?
- raise LibXML::XML::Error if hash[CONTENT_ROOT].to_s.length + content.length >= LIB_XML_LIMIT
- hash[CONTENT_ROOT] = hash[CONTENT_ROOT].to_s + content
- else
- sub_hash = insert_name_into_hash(hash, name)
- attributes_to_hash(sub_hash)
- if array?
- children_array_to_hash(sub_hash)
- elsif yaml?
- children_yaml_to_hash(sub_hash)
- else
- children_to_hash(sub_hash)
- end
- end
- hash
- end
+ node_hash = {}
- protected
-
- # Insert name into hash
- #
- # hash::
- # Hash to merge the converted element into.
- # name::
- # name to to merge into hash
- def insert_name_into_hash(hash, name)
- sub_hash = {}
- if hash[name]
- if !hash[name].kind_of? Array
- hash[name] = [hash[name]]
- end
- hash[name] << sub_hash
- else
- hash[name] = sub_hash
- end
- sub_hash
+ # Insert node hash into parent hash correctly.
+ case hash[name]
+ when Array then hash[name] << node_hash
+ when Hash then hash[name] = [hash[name], node_hash]
+ when nil then hash[name] = node_hash
end
- # Insert children into hash
- #
- # hash::
- # Hash to merge the children into.
- def children_to_hash(hash={})
- each { |child| child.to_hash(hash) }
-
- if hash.length > 1 && hash[CONTENT_ROOT].blank?
- hash.delete(CONTENT_ROOT)
+ # Handle child elements
+ each_child do |c|
+ if c.element?
+ c.to_hash(node_hash)
+ elsif c.text? || c.cdata?
+ node_hash[CONTENT_ROOT] ||= ''
+ node_hash[CONTENT_ROOT] << c.content
end
-
- attributes_to_hash(hash)
- hash
end
- # Convert xml attributes to hash
- #
- # hash::
- # Hash to merge the attributes into
- def attributes_to_hash(hash={})
- each_attr { |attr| hash[attr.name] = attr.value }
- hash
+ # Remove content node if it is blank
+ if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank?
+ node_hash.delete(CONTENT_ROOT)
end
- # Convert array into hash
- #
- # hash::
- # Hash to merge the array into
- def children_array_to_hash(hash={})
- hash[child.name] = map do |child|
- returning({}) { |sub_hash| child.children_to_hash(sub_hash) }
- end
- hash
- end
-
- # Convert yaml into hash
- #
- # hash::
- # Hash to merge the yaml into
- def children_yaml_to_hash(hash = {})
- hash[CONTENT_ROOT] = content unless content.blank?
- hash
- end
-
- # Check if child is of type array
- def array?
- child? && child.next? && child.name == child.next.name
- end
-
- # Check if child is of type yaml
- def yaml?
- attributes.collect{|x| x.value}.include?('yaml')
- end
+ # Handle attributes
+ each_attr { |a| node_hash[a.name] = a.value }
+ hash
+ end
end
end
end
diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
new file mode 100644
index 0000000000..d7b2f4c5be
--- /dev/null
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -0,0 +1,84 @@
+require 'libxml'
+
+# = XmlMini LibXML implementation using a SAX-based parser
+module ActiveSupport
+ module XmlMini_LibXMLSAX
+ extend self
+
+ # Class that will build the hash while the XML document
+ # is being parsed using SAX events.
+ class HashBuilder
+
+ include LibXML::XML::SaxParser::Callbacks
+
+ CONTENT_KEY = '__content__'.freeze
+ HASH_SIZE_KEY = '__hash_size__'.freeze
+
+ attr_reader :hash
+
+ def current_hash
+ @hash_stack.last
+ end
+
+ def on_start_document
+ @hash = { CONTENT_KEY => '' }
+ @hash_stack = [@hash]
+ end
+
+ def on_end_document
+ @hash = @hash_stack.pop
+ @hash.delete(CONTENT_KEY)
+ end
+
+ def on_start_element(name, attrs = {})
+ new_hash = { CONTENT_KEY => '' }.merge(attrs)
+ new_hash[HASH_SIZE_KEY] = new_hash.size + 1
+
+ case current_hash[name]
+ when Array then current_hash[name] << new_hash
+ when Hash then current_hash[name] = [current_hash[name], new_hash]
+ when nil then current_hash[name] = new_hash
+ end
+
+ @hash_stack.push(new_hash)
+ end
+
+ def on_end_element(name)
+ if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == ''
+ current_hash.delete(CONTENT_KEY)
+ end
+ @hash_stack.pop
+ end
+
+ def on_characters(string)
+ current_hash[CONTENT_KEY] << string
+ end
+
+ alias_method :on_cdata_block, :on_characters
+ end
+
+ attr_accessor :document_class
+ self.document_class = HashBuilder
+
+ def parse(data)
+ if !data.respond_to?(:read)
+ data = StringIO.new(data || '')
+ end
+
+ char = data.getc
+ if char.nil?
+ {}
+ else
+ data.ungetc(char)
+
+ LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)
+ parser = LibXML::XML::SaxParser.io(data)
+ document = self.document_class.new
+
+ parser.callbacks = document
+ parser.parse
+ document.hash
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 17bacd8441..eb61a7fc22 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -1,4 +1,5 @@
require 'nokogiri'
+require 'active_support/core_ext/object/blank'
# = XmlMini Nokogiri implementation
module ActiveSupport
@@ -12,13 +13,13 @@ module ActiveSupport
if !data.respond_to?(:read)
data = StringIO.new(data || '')
end
-
+
char = data.getc
if char.nil?
{}
else
data.ungetc(char)
- doc = Nokogiri::XML(data) { |cfg| cfg.noblanks }
+ doc = Nokogiri::XML(data)
raise doc.errors.first if doc.errors.length > 0
doc.to_hash
end
@@ -32,39 +33,41 @@ module ActiveSupport
end
module Node #:nodoc:
- CONTENT_ROOT = '__content__'
+ CONTENT_ROOT = '__content__'.freeze
# Convert XML document to hash
#
# hash::
# Hash to merge the converted element into.
- def to_hash(hash = {})
- attributes = attributes_as_hash
- if hash[name]
- hash[name] = [hash[name]].flatten
- hash[name] << attributes
- else
- hash[name] ||= attributes
- end
+ def to_hash(hash={})
+ node_hash = {}
- children.each { |child|
- next if child.blank? && 'file' != self['type']
+ # Insert node hash into parent hash correctly.
+ case hash[name]
+ when Array then hash[name] << node_hash
+ when Hash then hash[name] = [hash[name], node_hash]
+ when nil then hash[name] = node_hash
+ end
- if child.text? || child.cdata?
- (attributes[CONTENT_ROOT] ||= '') << child.content
- next
+ # Handle child elements
+ children.each do |c|
+ if c.element?
+ c.to_hash(node_hash)
+ elsif c.text? || c.cdata?
+ node_hash[CONTENT_ROOT] ||= ''
+ node_hash[CONTENT_ROOT] << c.content
end
+ end
- child.to_hash attributes
- }
+ # Remove content node if it is blank and there are child tags
+ if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank?
+ node_hash.delete(CONTENT_ROOT)
+ end
- hash
- end
+ # Handle attributes
+ attribute_nodes.each { |a| node_hash[a.node_name] = a.value }
- def attributes_as_hash
- Hash[*(attribute_nodes.map { |node|
- [node.node_name, node.value]
- }.flatten)]
+ hash
end
end
end
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
new file mode 100644
index 0000000000..d538a9110f
--- /dev/null
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -0,0 +1,82 @@
+require 'nokogiri'
+
+# = XmlMini Nokogiri implementation using a SAX-based parser
+module ActiveSupport
+ module XmlMini_NokogiriSAX
+ extend self
+
+ # Class that will build the hash while the XML document
+ # is being parsed using SAX events.
+ class HashBuilder < Nokogiri::XML::SAX::Document
+
+ CONTENT_KEY = '__content__'.freeze
+ HASH_SIZE_KEY = '__hash_size__'.freeze
+
+ attr_reader :hash
+
+ def current_hash
+ @hash_stack.last
+ end
+
+ def start_document
+ @hash = {}
+ @hash_stack = [@hash]
+ end
+
+ def end_document
+ raise "Parse stack not empty!" if @hash_stack.size > 1
+ end
+
+ def error(error_message)
+ raise error_message
+ end
+
+ def start_element(name, attrs = [])
+ new_hash = { CONTENT_KEY => '' }
+ new_hash[attrs.shift] = attrs.shift while attrs.length > 0
+ new_hash[HASH_SIZE_KEY] = new_hash.size + 1
+
+ case current_hash[name]
+ when Array then current_hash[name] << new_hash
+ when Hash then current_hash[name] = [current_hash[name], new_hash]
+ when nil then current_hash[name] = new_hash
+ end
+
+ @hash_stack.push(new_hash)
+ end
+
+ def end_element(name)
+ if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == ''
+ current_hash.delete(CONTENT_KEY)
+ end
+ @hash_stack.pop
+ end
+
+ def characters(string)
+ current_hash[CONTENT_KEY] << string
+ end
+
+ alias_method :cdata_block, :characters
+ end
+
+ attr_accessor :document_class
+ self.document_class = HashBuilder
+
+ def parse(data)
+ if !data.respond_to?(:read)
+ data = StringIO.new(data || '')
+ end
+
+ char = data.getc
+ if char.nil?
+ {}
+ else
+ data.ungetc(char)
+ document = self.document_class.new
+ parser = Nokogiri::XML::SAX::Parser.new(document)
+ parser.parse(data)
+ document.hash
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index 18721eab19..2e978f01be 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -56,6 +56,31 @@ class Child < GrandParent
end
end
+class EmptyParent
+ include ActiveSupport::Callbacks
+
+ def performed?
+ @performed ||= false
+ end
+
+ define_callbacks :dispatch
+
+ def perform!
+ @performed = true
+ end
+
+ def dispatch
+ _run_dispatch_callbacks
+ self
+ end
+end
+
+class EmptyChild < EmptyParent
+ set_callback :dispatch, :before, :do_nothing
+
+ def do_nothing
+ end
+end
class BasicCallbacksTest < Test::Unit::TestCase
def setup
@@ -113,3 +138,13 @@ class InheritedCallbacksTest2 < Test::Unit::TestCase
assert_equal %w(before1 before2 update after2 after1), @update2.log
end
end
+
+class DynamicInheritedCallbacks < Test::Unit::TestCase
+ def test_callbacks_looks_to_the_superclass_before_running
+ child = EmptyChild.new.dispatch
+ assert !child.performed?
+ EmptyParent.set_callback :dispatch, :before, :perform!
+ child = EmptyChild.new.dispatch
+ assert child.performed?
+ end
+end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 4642bb1330..5b1d53ac7b 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -902,9 +902,11 @@ class HashToXmlTest < Test::Unit::TestCase
def test_expansion_count_is_limited
expected = {
- 'ActiveSupport::XmlMini_REXML' => 'RuntimeError',
- 'ActiveSupport::XmlMini_Nokogiri' => 'Nokogiri::XML::SyntaxError',
- 'ActiveSupport::XmlMini_LibXML' => 'LibXML::XML::Error',
+ 'ActiveSupport::XmlMini_REXML' => 'RuntimeError',
+ 'ActiveSupport::XmlMini_Nokogiri' => 'Nokogiri::XML::SyntaxError',
+ 'ActiveSupport::XmlMini_NokogiriSAX' => 'RuntimeError',
+ 'ActiveSupport::XmlMini_LibXML' => 'LibXML::XML::Error',
+ 'ActiveSupport::XmlMini_LibXMLSAX' => 'LibXML::XML::Error',
}[ActiveSupport::XmlMini.backend.name].constantize
assert_raise expected do
diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb
index 900c8052d6..83d03bccc6 100644
--- a/activesupport/test/xml_mini/libxml_engine_test.rb
+++ b/activesupport/test/xml_mini/libxml_engine_test.rb
@@ -184,6 +184,15 @@ class LibxmlEngineTest < Test::Unit::TestCase
eoxml
end
+ def test_children_with_blank_text_and_attribute
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products type="file"> </products>
+ </root>
+ eoxml
+ end
+
+
private
def assert_equal_rexml(xml)
hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
new file mode 100644
index 0000000000..864810099e
--- /dev/null
+++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
@@ -0,0 +1,194 @@
+require 'abstract_unit'
+require 'active_support/xml_mini'
+require 'active_support/core_ext/hash/conversions'
+
+begin
+ require 'libxml'
+rescue LoadError
+ # Skip libxml tests
+else
+
+class LibXMLSAXEngineTest < Test::Unit::TestCase
+ include ActiveSupport
+
+ def setup
+ @default_backend = XmlMini.backend
+ XmlMini.backend = 'LibXMLSAX'
+ end
+
+ def teardown
+ XmlMini.backend = @default_backend
+ end
+
+ def test_exception_thrown_on_expansion_attack
+ assert_raise LibXML::XML::Error do
+ attack_xml = <<-EOT
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE member [
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
+ <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
+ <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
+ ]>
+ <member>
+ &a;
+ </member>
+ EOT
+
+ Hash.from_xml(attack_xml)
+ end
+ end
+
+ def test_setting_libxml_as_backend
+ XmlMini.backend = 'LibXMLSAX'
+ assert_equal XmlMini_LibXMLSAX, XmlMini.backend
+ end
+
+ def test_blank_returns_empty_hash
+ assert_equal({}, XmlMini.parse(nil))
+ assert_equal({}, XmlMini.parse(''))
+ end
+
+ def test_array_type_makes_an_array
+ assert_equal_rexml(<<-eoxml)
+ <blog>
+ <posts type="array">
+ <post>a post</post>
+ <post>another post</post>
+ </posts>
+ </blog>
+ eoxml
+ end
+
+ def test_one_node_document_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products/>
+ eoxml
+ end
+
+ def test_one_node_with_attributes_document_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products foo="bar"/>
+ eoxml
+ end
+
+ def test_products_node_with_book_node_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ <book name="awesome" id="12345" />
+ </products>
+ eoxml
+ end
+
+ def test_products_node_with_two_book_nodes_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ <book name="awesome" id="12345" />
+ <book name="america" id="67890" />
+ </products>
+ eoxml
+ end
+
+ def test_single_node_with_content_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ hello world
+ </products>
+ eoxml
+ end
+
+ def test_children_with_children
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <book name="america" id="67890" />
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ hello everyone
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_non_adjacent_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ good
+ <products>
+ hello everyone
+ </products>
+ morning
+ </root>
+ eoxml
+ end
+
+ def test_parse_from_io
+ io = StringIO.new(<<-eoxml)
+ <root>
+ good
+ <products>
+ hello everyone
+ </products>
+ morning
+ </root>
+ eoxml
+ XmlMini.parse(io)
+ end
+
+ def test_children_with_simple_cdata
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <![CDATA[cdatablock]]>
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_multiple_cdata
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]>
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_text_and_cdata
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ hello <![CDATA[cdatablock]]>
+ morning
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_blank_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products> </products>
+ </root>
+ eoxml
+ end
+
+ private
+ def assert_equal_rexml(xml)
+ hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ assert_equal(hash, XmlMini.parse(xml))
+ end
+end
+
+end
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index e16f36acee..db0d7c5b02 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -159,17 +159,53 @@ class NokogiriEngineTest < Test::Unit::TestCase
XmlMini.parse(io)
end
- def test_children_with_cdata
+ def test_children_with_simple_cdata
assert_equal_rexml(<<-eoxml)
<root>
<products>
- hello <![CDATA[everyone]]>
+ <![CDATA[cdatablock]]>
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_multiple_cdata
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]>
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_text_and_cdata
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ hello <![CDATA[cdatablock]]>
morning
</products>
</root>
eoxml
end
+ def test_children_with_blank_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products> </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_blank_text_and_attribute
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products type="file"> </products>
+ </root>
+ eoxml
+ end
+
private
def assert_equal_rexml(xml)
hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
new file mode 100644
index 0000000000..1149d0fecc
--- /dev/null
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -0,0 +1,217 @@
+require 'abstract_unit'
+require 'active_support/xml_mini'
+require 'active_support/core_ext/hash/conversions'
+
+begin
+ require 'nokogiri'
+rescue LoadError
+ # Skip nokogiri tests
+else
+
+class NokogiriSAXEngineTest < Test::Unit::TestCase
+ include ActiveSupport
+
+ def setup
+ @default_backend = XmlMini.backend
+ XmlMini.backend = 'NokogiriSAX'
+ end
+
+ def teardown
+ XmlMini.backend = @default_backend
+ end
+
+ def test_file_from_xml
+ hash = Hash.from_xml(<<-eoxml)
+ <blog>
+ <logo type="file" name="logo.png" content_type="image/png">
+ </logo>
+ </blog>
+ eoxml
+ assert hash.has_key?('blog')
+ assert hash['blog'].has_key?('logo')
+
+ file = hash['blog']['logo']
+ assert_equal 'logo.png', file.original_filename
+ assert_equal 'image/png', file.content_type
+ end
+
+ def test_exception_thrown_on_expansion_attack
+ assert_raise RuntimeError do
+ attack_xml = <<-EOT
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE member [
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
+ <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
+ <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
+ ]>
+ <member>
+ &a;
+ </member>
+ EOT
+
+ Hash.from_xml(attack_xml)
+ end
+ end
+
+ def test_setting_nokogiri_as_backend
+ XmlMini.backend = 'Nokogiri'
+ assert_equal XmlMini_Nokogiri, XmlMini.backend
+ end
+
+ def test_blank_returns_empty_hash
+ assert_equal({}, XmlMini.parse(nil))
+ assert_equal({}, XmlMini.parse(''))
+ end
+
+ def test_array_type_makes_an_array
+ assert_equal_rexml(<<-eoxml)
+ <blog>
+ <posts type="array">
+ <post>a post</post>
+ <post>another post</post>
+ </posts>
+ </blog>
+ eoxml
+ end
+
+ def test_one_node_document_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products/>
+ eoxml
+ end
+
+ def test_one_node_with_attributes_document_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products foo="bar"/>
+ eoxml
+ end
+
+ def test_products_node_with_book_node_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ <book name="awesome" id="12345" />
+ </products>
+ eoxml
+ end
+
+ def test_products_node_with_two_book_nodes_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ <book name="awesome" id="12345" />
+ <book name="america" id="67890" />
+ </products>
+ eoxml
+ end
+
+ def test_single_node_with_content_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ hello world
+ </products>
+ eoxml
+ end
+
+ def test_children_with_children
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <book name="america" id="67890" />
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ hello everyone
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_non_adjacent_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ good
+ <products>
+ hello everyone
+ </products>
+ morning
+ </root>
+ eoxml
+ end
+
+ def test_parse_from_io
+ io = StringIO.new(<<-eoxml)
+ <root>
+ good
+ <products>
+ hello everyone
+ </products>
+ morning
+ </root>
+ eoxml
+ XmlMini.parse(io)
+ end
+
+ def test_children_with_simple_cdata
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <![CDATA[cdatablock]]>
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_multiple_cdata
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]>
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_text_and_cdata
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ hello <![CDATA[cdatablock]]>
+ morning
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_blank_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products> </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_blank_text_and_attribute
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products type="file"> </products>
+ </root>
+ eoxml
+ end
+
+ private
+ def assert_equal_rexml(xml)
+ hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ assert_equal(hash, XmlMini.parse(xml))
+ end
+end
+
+end
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 9fb3cd9f94..d69e3eea6a 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -1,9 +1,106 @@
-require "rails/core"
+require "pathname"
-%w(active_model active_record action_controller action_view action_mailer active_resource).each do |framework|
- begin
- require framework
- require "#{framework}/rails"
- rescue LoadError
+require 'active_support'
+require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/logger'
+require 'action_dispatch'
+
+require 'rails/initializable'
+require 'rails/application'
+require 'rails/railtie'
+require 'rails/plugin'
+require 'rails/railties_path'
+require 'rails/version'
+require 'rails/rack'
+require 'rails/paths'
+require 'rails/configuration'
+require 'rails/deprecation'
+require 'rails/ruby_version_check'
+
+# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the
+# multibyte safe operations. Plugin authors supporting other encodings
+# should override this behaviour and set the relevant +default_charset+
+# on ActionController::Base.
+#
+# For Ruby 1.9, UTF-8 is the default internal and external encoding.
+if RUBY_VERSION < '1.9'
+ $KCODE='u'
+else
+ Encoding.default_external = Encoding::UTF_8
+end
+
+RAILS_ENV = (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup unless defined?(RAILS_ENV)
+
+module Rails
+ # Needs to be duplicated from Active Support since its needed before Active
+ # Support is available. Here both Options and Hash are namespaced to prevent
+ # conflicts with other implementations AND with the classes residing in Active Support.
+ # ---
+ # TODO: w0t?
+ class << self
+ def application
+ @@application ||= nil
+ end
+
+ def application=(application)
+ @@application = application
+ end
+
+ # The Configuration instance used to configure the Rails environment
+ def configuration
+ application.configuration
+ end
+
+ def initialize!
+ application.initialize!
+ end
+
+ def initialized?
+ @initialized || false
+ end
+
+ def initialized=(initialized)
+ @initialized ||= initialized
+ end
+
+ def logger
+ if defined?(RAILS_DEFAULT_LOGGER)
+ RAILS_DEFAULT_LOGGER
+ else
+ nil
+ end
+ end
+
+ def backtrace_cleaner
+ @@backtrace_cleaner ||= begin
+ # Relies on ActiveSupport, so we have to lazy load to postpone definition until AS has been loaded
+ require 'rails/backtrace_cleaner'
+ Rails::BacktraceCleaner.new
+ end
+ end
+
+ def root
+ application && application.config.root
+ end
+
+ def env
+ @_env ||= ActiveSupport::StringInquirer.new(RAILS_ENV)
+ end
+
+ def cache
+ RAILS_CACHE
+ end
+
+ def version
+ VERSION::STRING
+ end
+
+ def public_path
+ @@public_path ||= self.root ? File.join(self.root, "public") : "public"
+ end
+
+ def public_path=(path)
+ @@public_path = path
+ end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
new file mode 100644
index 0000000000..7dfe2b8b63
--- /dev/null
+++ b/railties/lib/rails/all.rb
@@ -0,0 +1,15 @@
+require "rails"
+
+%w(
+ active_model
+ active_record
+ action_controller
+ action_view
+ action_mailer
+ active_resource
+).each do |framework|
+ begin
+ require "#{framework}/railtie"
+ rescue LoadError
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 5419b46f19..26ffbe5a58 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -92,8 +92,8 @@ module Rails
def plugins
@plugins ||= begin
plugin_names = config.plugins || [:all]
- Plugin.plugins.select { |p| plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) }.map { |p| p.new } +
- Plugin::Vendored.all(config.plugins || [:all], config.paths.vendor.plugins)
+ Railtie.plugins.select { |p| plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) }.map { |p| p.new } +
+ Plugin.all(config.plugins || [:all], config.paths.vendor.plugins)
end
end
@@ -269,16 +269,5 @@ module Rails
ActiveSupport::Dependencies.unhook!
end
end
-
- # For each framework, search for instrument file with Notifications hooks.
- #
- initializer :load_notifications_hooks do
- config.frameworks.each do |framework|
- begin
- require "#{framework}/notifications"
- rescue LoadError => e
- end
- end
- end
end
end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 086f67a419..f0a0d5e55e 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -3,7 +3,7 @@ require 'active_support/ordered_options'
module Rails
# Temporarily separate the plugin configuration class from the main
# configuration class while this bit is being cleaned up.
- class Plugin::Configuration
+ class Railtie::Configuration
def self.default
@default ||= new
@@ -45,12 +45,12 @@ module Rails
end
def config_keys
- ([ :active_support, :action_view, :action_mailer, :active_resource ] +
- Plugin.plugin_names).map { |n| n.to_s }.uniq
+ ([ :active_support, :action_view ] +
+ Railtie.plugin_names).map { |n| n.to_s }.uniq
end
end
- class Configuration < Plugin::Configuration
+ class Configuration < Railtie::Configuration
attr_accessor :after_initialize_blocks, :cache_classes,
:consider_all_requests_local, :dependency_loading, :gems,
:load_once_paths, :logger, :metals, :plugins,
@@ -59,7 +59,7 @@ module Rails
attr_writer :cache_store, :controller_paths,
:database_configuration_file, :eager_load_paths,
- :frameworks, :framework_root_path, :i18n, :load_paths,
+ :i18n, :load_paths,
:log_level, :log_path, :paths, :routes_configuration_file,
:view_path
@@ -119,6 +119,13 @@ module Rails
end
end
+ def frameworks(*args)
+ raise "config.frameworks in no longer supported. See the generated" \
+ "config/boot.rb for steps on how to limit the frameworks that" \
+ "will be loaded"
+ end
+ alias frameworks= frameworks
+
# Enable threaded mode. Allows concurrent requests to controller actions and
# multiple database connections. Also disables automatic dependency loading
# after boot, and disables reloading code on every request, as these are
@@ -134,21 +141,6 @@ module Rails
self
end
- def framework_paths
- paths = %w(railties railties/lib activesupport/lib)
- paths << 'actionpack/lib' if frameworks.include?(:action_controller) || frameworks.include?(:action_view)
-
- [:active_record, :action_mailer, :active_resource, :action_web_service].each do |framework|
- paths << "#{framework.to_s.gsub('_', '')}/lib" if frameworks.include?(framework)
- end
-
- paths.map { |dir| "#{framework_root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
- end
-
- def framework_root_path
- defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root}/vendor/rails"
- end
-
# Loads and returns the contents of the #database_configuration_file. The
# contents of the file are processed via ERB before being sent through
# YAML::load.
@@ -239,10 +231,6 @@ module Rails
@log_level ||= RAILS_ENV == 'production' ? :info : :debug
end
- def frameworks
- @frameworks ||= [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ]
- end
-
def i18n
@i18n ||= begin
i18n = ActiveSupport::OrderedOptions.new
diff --git a/railties/lib/rails/core.rb b/railties/lib/rails/core.rb
deleted file mode 100644
index ab95edc676..0000000000
--- a/railties/lib/rails/core.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require "pathname"
-
-require 'active_support'
-require 'active_support/core_ext/kernel/reporting'
-require 'active_support/core_ext/logger'
-require 'action_dispatch'
-
-require 'rails/initializable'
-require 'rails/application'
-require 'rails/plugin'
-require 'rails/railties_path'
-require 'rails/version'
-require 'rails/rack'
-require 'rails/paths'
-require 'rails/configuration'
-require 'rails/deprecation'
-require 'rails/ruby_version_check'
-
-# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the
-# multibyte safe operations. Plugin authors supporting other encodings
-# should override this behaviour and set the relevant +default_charset+
-# on ActionController::Base.
-#
-# For Ruby 1.9, UTF-8 is the default internal and external encoding.
-if RUBY_VERSION < '1.9'
- $KCODE='u'
-else
- Encoding.default_external = Encoding::UTF_8
-end
-
-RAILS_ENV = (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup unless defined?(RAILS_ENV)
-
-module Rails
- # Needs to be duplicated from Active Support since its needed before Active
- # Support is available. Here both Options and Hash are namespaced to prevent
- # conflicts with other implementations AND with the classes residing in Active Support.
- # ---
- # TODO: w0t?
- class << self
- def application
- @@application ||= nil
- end
-
- def application=(application)
- @@application = application
- end
-
- # The Configuration instance used to configure the Rails environment
- def configuration
- application.configuration
- end
-
- def initialize!
- application.initialize!
- end
-
- def initialized?
- @initialized || false
- end
-
- def initialized=(initialized)
- @initialized ||= initialized
- end
-
- def logger
- if defined?(RAILS_DEFAULT_LOGGER)
- RAILS_DEFAULT_LOGGER
- else
- nil
- end
- end
-
- def backtrace_cleaner
- @@backtrace_cleaner ||= begin
- # Relies on ActiveSupport, so we have to lazy load to postpone definition until AS has been loaded
- require 'rails/backtrace_cleaner'
- Rails::BacktraceCleaner.new
- end
- end
-
- def root
- application && application.config.root
- end
-
- def env
- @_env ||= ActiveSupport::StringInquirer.new(RAILS_ENV)
- end
-
- def cache
- RAILS_CACHE
- end
-
- def version
- VERSION::STRING
- end
-
- def public_path
- @@public_path ||= self.root ? File.join(self.root, "public") : "public"
- end
-
- def public_path=(path)
- @@public_path = path
- end
- end
-end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 2ba56bc3c5..6419961415 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -10,7 +10,7 @@ require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/inflections'
# TODO: Do not always push on vendored thor
-$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.1/lib")
+$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.3/lib")
require 'rails/generators/base'
require 'rails/generators/named_base'
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index c03836ed91..ee401b1fde 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -152,7 +152,7 @@ module Rails::Generators
inside "tmp" do
%w(sessions sockets cache pids).each do |dir|
- empty_directory_with_gitkeep(dir)
+ empty_directory(dir)
end
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 15dc553e53..ec0729db04 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -13,16 +13,8 @@ module <%= app_name.classify %>
# :all can be used as a placeholder for all plugins not explicitly named
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
- # Skip frameworks you're not going to use. To use Rails without a database,
- # you must remove the Active Record framework.
-<% if options[:skip_activerecord] -%>
- config.frameworks -= [ :active_record ]
-<% else -%>
- # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
-
# Activate observers that should always be running
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
-<% end -%>
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 5aa49ca5e6..6de1725260 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -13,4 +13,13 @@ else
require 'rubygems'
end
-require 'rails'
+require 'rails/all'
+# To pick the frameworks you want, remove 'require "rails/all"'
+# and list the framework railties that you want:
+#
+# require "active_model/railtie"
+# require "active_record/railtie"
+# require "action_controller/railtie"
+# require "action_view/railtie"
+# require "action_mailer/railtie"
+# require "active_resource/railtie" \ No newline at end of file
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index e154e9b706..9cc6b9c35b 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -1,106 +1,64 @@
module Rails
- class Plugin
- include Initializable
-
- def self.plugin_name(plugin_name = nil)
- @plugin_name ||= name.demodulize.underscore
- @plugin_name = plugin_name if plugin_name
- @plugin_name
- end
-
- def self.inherited(klass)
- @plugins ||= []
- @plugins << klass unless klass == Vendored
- end
-
- def self.plugins
- @plugins
- end
+ class Plugin < Railtie
+ def self.all(list, paths)
+ plugins = []
+ paths.each do |path|
+ Dir["#{path}/*"].each do |plugin_path|
+ plugin = new(plugin_path)
+ next unless list.include?(plugin.name) || list.include?(:all)
+ plugins << plugin
+ end
+ end
- def self.plugin_names
- plugins.map { |p| p.plugin_name }
+ plugins.sort_by do |p|
+ [list.index(p.name) || list.index(:all), p.name.to_s]
+ end
end
- def self.config
- Configuration.default
- end
+ attr_reader :name, :path
- def self.rake_tasks(&blk)
- @rake_tasks ||= []
- @rake_tasks << blk if blk
- @rake_tasks
+ def initialize(path)
+ @name = File.basename(path).to_sym
+ @path = path
end
- def rake_tasks
- self.class.rake_tasks
+ def load_paths
+ Dir["#{path}/{lib}", "#{path}/app/{models,controllers,helpers}"]
end
def load_tasks
- return unless rake_tasks
- rake_tasks.each { |blk| blk.call }
+ Dir["#{path}/**/tasks/**/*.rake"].sort.each { |ext| load ext }
end
- class Vendored < Plugin
- def self.all(list, paths)
- plugins = []
- paths.each do |path|
- Dir["#{path}/*"].each do |plugin_path|
- plugin = new(plugin_path)
- next unless list.include?(plugin.name) || list.include?(:all)
- plugins << plugin
- end
- end
-
- plugins.sort_by do |p|
- [list.index(p.name) || list.index(:all), p.name.to_s]
- end
- end
-
- attr_reader :name, :path
-
- def initialize(path)
- @name = File.basename(path).to_sym
- @path = path
- end
-
- def load_paths
- Dir["#{path}/{lib}", "#{path}/app/{models,controllers,helpers}"]
- end
-
- def load_tasks
- Dir["#{path}/**/tasks/**/*.rake"].sort.each { |ext| load ext }
- end
-
- initializer :add_to_load_path, :after => :set_autoload_paths do |app|
- load_paths.each do |path|
- $LOAD_PATH << path
- require "active_support/dependencies"
+ initializer :add_to_load_path, :after => :set_autoload_paths do |app|
+ load_paths.each do |path|
+ $LOAD_PATH << path
+ require "active_support/dependencies"
- ActiveSupport::Dependencies.load_paths << path
+ ActiveSupport::Dependencies.load_paths << path
- unless app.config.reload_plugins
- ActiveSupport::Dependencies.load_once_paths << path
- end
+ unless app.config.reload_plugins
+ ActiveSupport::Dependencies.load_once_paths << path
end
end
+ end
- initializer :load_init_rb, :before => :load_application_initializers do |app|
- file = "#{@path}/init.rb"
- config = app.config
- eval File.read(file), binding, file if File.file?(file)
- end
+ initializer :load_init_rb, :before => :load_application_initializers do |app|
+ file = "#{@path}/init.rb"
+ config = app.config
+ eval File.read(file), binding, file if File.file?(file)
+ end
- initializer :add_view_paths, :after => :initialize_framework_views do
- ActionController::Base.view_paths.concat ["#{path}/app/views"] if File.directory?("#{path}/app/views")
- end
+ initializer :add_view_paths, :after => :initialize_framework_views do
+ ActionController::Base.view_paths.concat ["#{path}/app/views"] if File.directory?("#{path}/app/views")
+ end
- initializer :add_routing_file, :after => :initialize_routing do |app|
- routing_file = "#{path}/config/routes.rb"
- if File.exist?(routing_file)
- app.route_configuration_files << routing_file
- app.reload_routes!
- end
+ initializer :add_routing_file, :after => :initialize_routing do |app|
+ routing_file = "#{path}/config/routes.rb"
+ if File.exist?(routing_file)
+ app.route_configuration_files << routing_file
+ app.reload_routes!
end
end
end
-end
+end \ No newline at end of file
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
new file mode 100644
index 0000000000..ff28ade35d
--- /dev/null
+++ b/railties/lib/rails/railtie.rb
@@ -0,0 +1,43 @@
+module Rails
+ class Railtie
+ include Initializable
+
+ def self.plugin_name(plugin_name = nil)
+ @plugin_name ||= name.demodulize.underscore
+ @plugin_name = plugin_name if plugin_name
+ @plugin_name
+ end
+
+ def self.inherited(klass)
+ @plugins ||= []
+ @plugins << klass unless klass == Plugin
+ end
+
+ def self.plugins
+ @plugins
+ end
+
+ def self.plugin_names
+ plugins.map { |p| p.plugin_name }
+ end
+
+ def self.config
+ Configuration.default
+ end
+
+ def self.rake_tasks(&blk)
+ @rake_tasks ||= []
+ @rake_tasks << blk if blk
+ @rake_tasks
+ end
+
+ def rake_tasks
+ self.class.rake_tasks
+ end
+
+ def load_tasks
+ return unless rake_tasks
+ rake_tasks.each { |blk| blk.call }
+ end
+ end
+end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb
deleted file mode 100644
index 650253d648..0000000000
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Thor
- VERSION = "0.12.1".freeze
-end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/CHANGELOG.rdoc b/railties/lib/rails/vendor/thor-0.12.3/CHANGELOG.rdoc
index 606a0cdb52..d02fc43e84 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/CHANGELOG.rdoc
+++ b/railties/lib/rails/vendor/thor-0.12.3/CHANGELOG.rdoc
@@ -1,9 +1,7 @@
-== TODO
-
-* Improve spec coverage for Thor::Runner
-
-== 0.12, released 2009-11-06
+== 0.12, released 2010-01-02
+* Removed rr in favor to rspec mock framework
+* Improved output for thor -T
* [#7] Do not force white color on status
* [#8] Yield a block with the filename on directory
@@ -17,7 +15,7 @@
* thor help now show information about any class/task. All those calls are
possible:
-
+
thor help describe
thor help describe:amazing
@@ -47,7 +45,7 @@
are in the 'standard' group. Running 'thor -T' will only show the standard
tasks - adding --all will show all tasks. You can also filter on a specific
group using the --group option: thor -T --group advanced
-
+
== 0.9.6, released 2008-09-13
* Generic improvements
diff --git a/railties/lib/rails/vendor/thor-0.12.1/LICENSE b/railties/lib/rails/vendor/thor-0.12.3/LICENSE
index 98722da459..98722da459 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/LICENSE
+++ b/railties/lib/rails/vendor/thor-0.12.3/LICENSE
diff --git a/railties/lib/rails/vendor/thor-0.12.1/README.rdoc b/railties/lib/rails/vendor/thor-0.12.3/README.rdoc
index ee545f3d97..ee545f3d97 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/README.rdoc
+++ b/railties/lib/rails/vendor/thor-0.12.3/README.rdoc
diff --git a/railties/lib/rails/vendor/thor-0.12.1/Thorfile b/railties/lib/rails/vendor/thor-0.12.3/Thorfile
index ff1cb4498a..ff1cb4498a 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/Thorfile
+++ b/railties/lib/rails/vendor/thor-0.12.3/Thorfile
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor.rb
index 68944f140d..d4d8fbd64d 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor.rb
@@ -78,14 +78,14 @@ class Thor
@method_options
end
- # Adds an option to the set of class options. If :for is given as option,
+ # Adds an option to the set of method options. If :for is given as option,
# it allows you to change the options from a previous defined task.
#
# def previous_task
# # magic
# end
#
- # method_options :foo => :bar, :for => :previous_task
+ # method_option :foo => :bar, :for => :previous_task
#
# def next_task
# # magic
@@ -101,7 +101,6 @@ class Thor
# :default - Default value for this argument. It cannot be required and have default values.
# :aliases - Aliases for this option.
# :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
- # :group - The group for this options. Use by class options to output options in different levels.
# :banner - String to show on usage notes.
#
def method_option(name, options={})
@@ -140,49 +139,48 @@ class Thor
end
end
- # Prints help information. If a task name is given, it shows information
- # only about the specific task.
+ # Prints help information for the given task.
#
# ==== Parameters
- # meth<String>:: An optional task name to print usage information about.
+ # shell<Thor::Shell>
+ # task_name<String>
+ #
+ def task_help(shell, task_name)
+ task = all_tasks[task_name]
+ raise UndefinedTaskError, "task '#{task_name}' could not be found in namespace '#{self.namespace}'" unless task
+
+ shell.say "Usage:"
+ shell.say " #{banner(task)}"
+ shell.say
+ class_options_help(shell, nil => task.options.map { |_, o| o })
+ shell.say task.description
+ end
+
+ # Prints help information for this class.
#
- # ==== Options
- # namespace:: When true, shows the namespace in the output before the usage.
- # skip_inherited:: When true, does not show tasks from superclass.
+ # ==== Parameters
+ # shell<Thor::Shell>
#
- def help(shell, meth=nil, options={})
- meth, options = nil, meth if meth.is_a?(Hash)
-
- if meth
- task = all_tasks[meth]
- raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task
-
- shell.say "Usage:"
- shell.say " #{banner(task, options[:namespace], false)}"
- shell.say
- class_options_help(shell, "Class", :Method => task.options.map { |_, o| o })
- shell.say task.description
- else
- list = (options[:short] ? tasks : all_tasks).map do |_, task|
- item = [ banner(task, options[:namespace]) ]
- item << "# #{task.short_description}" if task.short_description
- item << " "
- end
-
- options[:ident] ||= 2
- if options[:short]
- shell.print_list(list, :ident => options[:ident])
- else
- shell.say "Tasks:"
- shell.print_list(list, :ident => options[:ident])
- end
+ def help(shell)
+ list = printable_tasks
+ Thor::Util.thor_classes_in(self).each do |klass|
+ list += klass.printable_tasks(false)
+ end
+ list.sort!{ |a,b| a[0] <=> b[0] }
- Thor::Util.thor_classes_in(self).each do |subclass|
- namespace = options[:namespace] == true || subclass.namespace.gsub(/^#{self.namespace}:/, '')
- subclass.help(shell, options.merge(:short => true, :namespace => namespace))
- end
+ shell.say "Tasks:"
+ shell.print_table(list, :ident => 2, :truncate => true)
+ shell.say
+ class_options_help(shell)
+ end
- class_options_help(shell, "Class") unless options[:short]
+ # Returns tasks ready to be printed.
+ def printable_tasks(all=true)
+ (all ? all_tasks : tasks).map do |_, task|
+ item = []
+ item << banner(task)
+ item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
+ item
end
end
@@ -193,8 +191,8 @@ class Thor
# the task that is going to be invoked and a boolean which indicates if
# the namespace should be displayed as arguments.
#
- def banner(task, namespace=true, show_options=true)
- task.formatted_usage(self, namespace, show_options)
+ def banner(task)
+ "thor " + task.formatted_usage(self)
end
def baseclass #:nodoc:
@@ -237,6 +235,6 @@ class Thor
desc "help [TASK]", "Describe available tasks or one specific task"
def help(task=nil)
- self.class.help(shell, task, :namespace => task && task.include?(?:))
+ task ? self.class.task_help(shell, task) : self.class.help(shell)
end
end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions.rb
index 4bfb7c2870..da98444bf2 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions.rb
@@ -1,4 +1,5 @@
require 'fileutils'
+require 'thor/core_ext/file_binary_read'
Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action|
require action
@@ -38,17 +39,17 @@ class Thor
# Add runtime options that help actions execution.
#
def add_runtime_options!
- class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
- :desc => "Run but do not make any changes"
-
class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
:desc => "Overwrite files that already exist"
- class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
- :desc => "Skip files that already exist"
+ class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
+ :desc => "Run but do not make any changes"
class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
:desc => "Supress status output"
+
+ class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
+ :desc => "Skip files that already exist"
end
end
@@ -114,7 +115,7 @@ class Thor
@source_paths ||= self.class.source_paths_for_search
end
- # Receives a file or directory and search for it in the source paths.
+ # Receives a file or directory and search for it in the source paths.
#
def find_in_source_paths(file)
relative_root = relative_to_original_destination_root(destination_root, false)
@@ -222,7 +223,7 @@ class Thor
run "#{command}", config.merge(:with => Thor::Util.ruby_command)
end
- # Run a thor command. A hash of options can be given and it's converted to
+ # Run a thor command. A hash of options can be given and it's converted to
# switches.
#
# ==== Parameters
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/create_file.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/create_file.rb
index a3d9296823..6e0eeb43e2 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/create_file.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/create_file.rb
@@ -42,7 +42,7 @@ class Thor
# Boolean:: true if it is identical, false otherwise.
#
def identical?
- exists? && File.read(destination) == render
+ exists? && File.binread(destination) == render
end
# Holds the content to be added to the file.
@@ -58,7 +58,7 @@ class Thor
def invoke!
invoke_with_conflict_check do
FileUtils.mkdir_p(File.dirname(destination))
- File.open(destination, 'w'){ |f| f.write render }
+ File.open(destination, 'wb') { |f| f.write render }
end
given_destination
end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/directory.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/directory.rb
index 2e0b459fa3..2e0b459fa3 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/directory.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/directory.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/empty_directory.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/empty_directory.rb
index 484cb820f8..484cb820f8 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/empty_directory.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/empty_directory.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/file_manipulation.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/file_manipulation.rb
index 8a45c83f25..44d6836c10 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/file_manipulation.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/file_manipulation.rb
@@ -23,7 +23,7 @@ class Thor
source = File.expand_path(find_in_source_paths(source.to_s))
create_file destination, nil, config do
- content = File.read(source)
+ content = File.binread(source)
content = block.call(content) if block
content
end
@@ -48,7 +48,7 @@ class Thor
#
def get(source, destination=nil, config={}, &block)
source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\//
- render = open(source).read
+ render = File.binread(source)
destination ||= if block_given?
block.arity == 1 ? block.call(render) : block.call
@@ -80,7 +80,7 @@ class Thor
context = instance_eval('binding')
create_file destination, nil, config do
- content = ERB.new(::File.read(source), nil, '-').result(context)
+ content = ERB.new(::File.binread(source), nil, '-').result(context)
content = block.call(content) if block
content
end
@@ -193,7 +193,7 @@ class Thor
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
unless options[:pretend]
- content = File.read(path)
+ content = File.binread(path)
content.gsub!(flag, *args, &block)
File.open(path, 'wb') { |file| file.write(content) }
end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/inject_into_file.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/inject_into_file.rb
index 6b0b42ea02..350ab73862 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/inject_into_file.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/inject_into_file.rb
@@ -11,7 +11,7 @@ class Thor
# data<String>:: Data to add to the file. Can be given as a block.
# config<Hash>:: give :verbose => false to not log the status and the flag
# for injection (:after or :before).
- #
+ #
# ==== Examples
#
# inject_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
@@ -90,7 +90,7 @@ class Thor
#
def replace!(regexp, string)
unless base.options[:pretend]
- content = File.read(destination)
+ content = File.binread(destination)
content.gsub!(regexp, string)
File.open(destination, 'wb') { |file| file.write(content) }
end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/base.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/base.rb
index 700d794123..aae4cdb89c 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/base.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/base.rb
@@ -92,6 +92,8 @@ class Thor
end
module ClassMethods
+ attr_accessor :debugging
+
# Adds an argument to the class and creates an attr_accessor for it.
#
# Arguments are different from options in several aspects. The first one
@@ -347,10 +349,11 @@ class Thor
# Default way to start generators from the command line.
#
def start(given_args=ARGV, config={})
+ self.debugging = given_args.include?("--debug")
config[:shell] ||= Thor::Base.shell.new
yield
rescue Thor::Error => e
- if given_args.include?("--debug")
+ if debugging
raise e
else
config[:shell].error e.message
@@ -361,48 +364,43 @@ class Thor
protected
# Prints the class options per group. If an option does not belong to
- # any group, it uses the ungrouped name value. This method provide to
- # hooks to add extra options, one of them if the third argument called
- # extra_group that should be a hash in the format :group => Array[Options].
- #
- # The second is by returning a lambda used to print values. The lambda
- # requires two options: the group name and the array of options.
+ # any group, it's printed as Class option.
#
- def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc:
- groups = {}
-
+ def class_options_help(shell, groups={}) #:nodoc:
+ # Group options by group
class_options.each do |_, value|
groups[value.group] ||= []
groups[value.group] << value
end
- printer = proc do |group_name, options|
- list = []
- padding = options.collect{ |o| o.aliases.size }.max.to_i * 4
+ # Deal with default group
+ global_options = groups.delete(nil) || []
+ print_options(shell, global_options)
+
+ # Print all others
+ groups.each do |group_name, options|
+ print_options(shell, options, group_name)
+ end
+ end
+
+ # Receives a set of options and print them.
+ def print_options(shell, options, group_name=nil)
+ return if options.empty?
- options.each do |option|
- item = [ option.usage(padding) ]
- item.push(option.description ? "# #{option.description}" : "")
+ list = []
+ padding = options.collect{ |o| o.aliases.size }.max.to_i * 4
- list << item
- list << [ "", "# Default: #{option.default}" ] if option.show_default?
- end
+ options.each do |option|
+ item = [ option.usage(padding) ]
+ item.push(option.description ? "# #{option.description}" : "")
- unless list.empty?
- shell.say(group_name ? "#{group_name} options:" : "Options:")
- shell.print_table(list, :ident => 2)
- shell.say ""
- end
+ list << item
+ list << [ "", "# Default: #{option.default}" ] if option.show_default?
end
- # Deal with default group
- global_options = groups.delete(nil) || []
- printer.call(ungrouped_name, global_options) if global_options
-
- # Print all others
- groups = extra_group.merge(groups) if extra_group
- groups.each(&printer)
- printer
+ shell.say(group_name ? "#{group_name} options:" : "Options:")
+ shell.print_table(list, :ident => 2)
+ shell.say ""
end
# Raises an error if the word given is a Thor reserved word.
diff --git a/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/file_binary_read.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/file_binary_read.rb
new file mode 100644
index 0000000000..d6af7e44b0
--- /dev/null
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/file_binary_read.rb
@@ -0,0 +1,9 @@
+class File #:nodoc:
+
+ unless File.respond_to?(:binread)
+ def self.binread(file)
+ File.open(file, 'rb') { |f| f.read }
+ end
+ end
+
+end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/hash_with_indifferent_access.rb
index 40d201d9e4..78bc5cf4bf 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/hash_with_indifferent_access.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/hash_with_indifferent_access.rb
@@ -65,7 +65,7 @@ class Thor
else
self[$1] == args.first
end
- else
+ else
self[method]
end
end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/ordered_hash.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/ordered_hash.rb
index 27fea5bb35..27fea5bb35 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/ordered_hash.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/ordered_hash.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/error.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/error.rb
index f9b31a35d1..f9b31a35d1 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/error.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/error.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/group.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb
index 021a067a3e..a585b37b73 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/group.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb
@@ -41,16 +41,12 @@ class Thor::Group
# ==== Options
# short:: When true, shows only usage.
#
- def help(shell, options={})
- if options[:short]
- shell.say banner
- else
- shell.say "Usage:"
- shell.say " #{banner}"
- shell.say
- class_options_help(shell)
- shell.say self.desc if self.desc
- end
+ def help(shell)
+ shell.say "Usage:"
+ shell.say " #{banner}\n"
+ shell.say
+ class_options_help(shell)
+ shell.say self.desc if self.desc
end
# Stores invocations for this class merging with superclass values.
@@ -132,7 +128,7 @@ class Thor::Group
names.each do |name|
unless class_options.key?(name)
- raise ArgumentError, "You have to define the option #{name.inspect} " <<
+ raise ArgumentError, "You have to define the option #{name.inspect} " <<
"before setting invoke_from_option."
end
@@ -177,15 +173,11 @@ class Thor::Group
# Overwrite class options help to allow invoked generators options to be
# shown recursively when invoking a generator.
#
- def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc:
- group_options = {}
-
- get_options_from_invocations(group_options, class_options) do |klass|
- klass.send(:get_options_from_invocations, group_options, class_options)
+ def class_options_help(shell, groups={}) #:nodoc:
+ get_options_from_invocations(groups, class_options) do |klass|
+ klass.send(:get_options_from_invocations, groups, class_options)
end
-
- group_options.merge!(extra_group) if extra_group
- super(shell, ungrouped_name, group_options)
+ super(shell, groups)
end
# Get invocations array and merge options from invocations. Those
@@ -218,13 +210,26 @@ class Thor::Group
end
end
+ # Returns tasks ready to be printed.
+ def printable_tasks(*)
+ item = []
+ item << banner
+ item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
+ [item]
+ end
+
protected
# The banner for this class. You can customize it if you are invoking the
# thor class by another ways which is not the Thor::Runner.
#
def banner
- "#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}"
+ "thor #{self_task.formatted_usage(self, false)}"
+ end
+
+ # Represents the whole class as a task.
+ def self_task #:nodoc:
+ Thor::Task::Dynamic.new(self.namespace, class_options)
end
def baseclass #:nodoc:
@@ -243,7 +248,6 @@ class Thor::Group
# Shortcut to invoke with padding and block handling. Use internally by
# invoke and invoke_from_option class methods.
- #
def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
shell.padding += 1
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/invocation.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/invocation.rb
index 32e6a72454..32e6a72454 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/invocation.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/invocation.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser.rb
index 57a3f6e1a5..57a3f6e1a5 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/argument.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/argument.rb
index aa8ace4719..aa8ace4719 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/argument.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/argument.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/arguments.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/arguments.rb
index fb5d965e06..fb5d965e06 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/arguments.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/arguments.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/option.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/option.rb
index e09b4901e2..9e40ec73fa 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/option.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/option.rb
@@ -36,7 +36,7 @@ class Thor
# string (--foo=value) or booleans (just --foo).
#
# By default all options are optional, unless :required is given.
- #
+ #
def self.parse(key, value)
if key.is_a?(Array)
name, *aliases = key
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/options.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/options.rb
index 75092308b5..75092308b5 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/options.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/options.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/rake_compat.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/rake_compat.rb
index 0d0757fdda..0d0757fdda 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/rake_compat.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/rake_compat.rb
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/runner.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/runner.rb
index 079f9e0c65..f197081e3f 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/runner.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/runner.rb
@@ -36,7 +36,7 @@ class Thor::Runner < Thor #:nodoc:
def install(name)
initialize_thorfiles
- # If a directory name is provided as the argument, look for a 'main.thor'
+ # If a directory name is provided as the argument, look for a 'main.thor'
# task in said directory.
begin
if File.directory?(File.expand_path(name))
@@ -124,11 +124,7 @@ class Thor::Runner < Thor #:nodoc:
method_options :internal => :boolean
def installed
initialize_thorfiles(nil, true)
-
- klasses = Thor::Base.subclasses
- klasses -= [Thor, Thor::Runner] unless options["internal"]
-
- display_klasses(true, klasses)
+ display_klasses(true, options["internal"])
end
desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
@@ -144,11 +140,15 @@ class Thor::Runner < Thor #:nodoc:
(options[:all] || k.group == group) && k.namespace =~ search
end
- display_klasses(false, klasses)
+ display_klasses(false, false, klasses)
end
private
+ def self.banner(task)
+ "thor " + task.formatted_usage(self, false)
+ end
+
def thor_root
Thor::Util.thor_root
end
@@ -156,7 +156,7 @@ class Thor::Runner < Thor #:nodoc:
def thor_yaml
@thor_yaml ||= begin
yaml_file = File.join(thor_root, "thor.yml")
- yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
+ yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
yaml || {}
end
end
@@ -215,9 +215,6 @@ class Thor::Runner < Thor #:nodoc:
# 5. c:\ <-- no Thorfiles found!
#
def thorfiles(relevant_to=nil, skip_lookup=false)
- # TODO Remove this dealing with deprecated thor when :namespaces: is available as constants
- save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml)
-
thorfiles = []
unless skip_lookup
@@ -253,47 +250,54 @@ class Thor::Runner < Thor #:nodoc:
# Display information about the given klasses. If with_module is given,
# it shows a table with information extracted from the yaml file.
#
- def display_klasses(with_modules=false, klasses=Thor.subclasses)
- klasses -= [Thor, Thor::Runner] unless with_modules
+ def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
+ klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
+
raise Error, "No Thor tasks available" if klasses.empty?
+ show_modules if with_modules && !thor_yaml.empty?
- if with_modules && !thor_yaml.empty?
- info = []
- labels = ["Modules", "Namespaces"]
+ # Remove subclasses
+ klasses.dup.each do |klass|
+ klasses -= Thor::Util.thor_classes_in(klass)
+ end
- info << labels
- info << [ "-" * labels[0].size, "-" * labels[1].size ]
+ list = Hash.new { |h,k| h[k] = [] }
+ groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
- thor_yaml.each do |name, hash|
- info << [ name, hash[:namespaces].join(", ") ]
- end
+ # Get classes which inherit from Thor
+ (klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) }
- print_table info
- say ""
- end
+ # Get classes which inherit from Thor::Base
+ groups.map! { |k| k.printable_tasks(false).first }
+ list["root"] = groups
- unless klasses.empty?
- klasses.dup.each do |klass|
- klasses -= Thor::Util.thor_classes_in(klass)
- end
+ # Order namespaces with default coming first
+ list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
+ list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
+ end
- klasses.each { |k| display_tasks(k) }
- else
- say "\033[1;34mNo Thor tasks available\033[0m"
- end
+ def display_tasks(namespace, list) #:nodoc:
+ list.sort!{ |a,b| a[0] <=> b[0] }
+
+ say shell.set_color(namespace, :blue, true)
+ say "-" * namespace.size
+
+ print_table(list, :truncate => true)
+ say
end
- # Display tasks from the given Thor class.
- #
- def display_tasks(klass)
- unless klass.tasks.empty?
- base = klass.namespace
+ def show_modules #:nodoc:
+ info = []
+ labels = ["Modules", "Namespaces"]
- color = base == "default" ? :magenta : :blue
- say shell.set_color(base, color, true)
- say "-" * base.length
+ info << labels
+ info << [ "-" * labels[0].size, "-" * labels[1].size ]
- klass.help(shell, :short => true, :ident => 0, :namespace => true)
+ thor_yaml.each do |name, hash|
+ info << [ name, hash[:namespaces].join(", ") ]
end
+
+ print_table info
+ say ""
end
end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell.rb
index 1dc8f0e5b4..64a173de83 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell.rb
@@ -22,7 +22,7 @@ class Thor
end
module Shell
- SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table]
+ SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_table]
# Add shell to initialize config values.
#
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/basic.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/basic.rb
index f6be3575ca..a11f45b4e9 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/basic.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/basic.rb
@@ -75,30 +75,6 @@ class Thor
!yes?(statement, color)
end
- # Prints a list of items.
- #
- # ==== Parameters
- # list<Array[String, String, ...]>
- #
- # ==== Options
- # mode:: Can be :rows or :inline. Defaults to :rows.
- # ident:: Ident each item with the value given.
- #
- def print_list(list, options={})
- return if list.empty?
-
- ident = " " * (options[:ident] || 0)
- content = case options[:mode]
- when :inline
- last = list.pop
- "#{list.join(", ")}, and #{last}"
- else # rows
- ident + list.join("\n#{ident}")
- end
-
- $stdout.puts content
- end
-
# Prints a table.
#
# ==== Parameters
@@ -110,20 +86,26 @@ class Thor
def print_table(table, options={})
return if table.empty?
- formats = []
+ formats, ident = [], options[:ident].to_i
+ options[:truncate] = terminal_width if options[:truncate] == true
+
0.upto(table.first.length - 2) do |i|
maxima = table.max{ |a,b| a[i].size <=> b[i].size }[i].size
formats << "%-#{maxima + 2}s"
end
- formats[0] = formats[0].insert(0, " " * options[:ident]) if options[:ident]
+ formats[0] = formats[0].insert(0, " " * ident)
formats << "%s"
table.each do |row|
+ sentence = ""
+
row.each_with_index do |column, i|
- $stdout.print formats[i] % column.to_s
+ sentence << formats[i] % column.to_s
end
- $stdout.puts
+
+ sentence = truncate(sentence, options[:truncate]) if options[:truncate]
+ $stdout.puts sentence
end
end
@@ -214,6 +196,44 @@ HELP
base && base.options[:quiet]
end
+ # This code was copied from Rake, available under MIT-LICENSE
+ # Copyright (c) 2003, 2004 Jim Weirich
+ def terminal_width
+ if ENV['THOR_COLUMNS']
+ result = ENV['THOR_COLUMNS'].to_i
+ else
+ result = unix? ? dynamic_width : 80
+ end
+ (result < 10) ? 80 : result
+ rescue
+ 80
+ end
+
+ # Calculate the dynamic width of the terminal
+ def dynamic_width
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
+ end
+
+ def dynamic_width_stty
+ %x{stty size 2>/dev/null}.split[1].to_i
+ end
+
+ def dynamic_width_tput
+ %x{tput cols 2>/dev/null}.to_i
+ end
+
+ def unix?
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
+ end
+
+ def truncate(string, width)
+ if string.length <= width
+ string
+ else
+ ( string[0, width-3] || "" ) + "..."
+ end
+ end
+
end
end
end
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/color.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/color.rb
index 24704f7885..b2bc66dfba 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/color.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/color.rb
@@ -63,7 +63,7 @@ class Thor
#
def show_diff(destination, content) #:nodoc:
if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil?
- actual = File.read(destination).to_s.split("\n")
+ actual = File.binread(destination).to_s.split("\n")
content = content.to_s.split("\n")
Diff::LCS.sdiff(actual, content).each do |diff|
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/task.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/task.rb
index 91c7564d3f..5c8877591b 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/task.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/task.rb
@@ -1,11 +1,11 @@
class Thor
class Task < Struct.new(:name, :description, :usage, :options)
+ FILE_REGEXP = /^#{Regexp.escape(File.expand_path(__FILE__))}:[\w:]+ `run'$/
# A dynamic task that handles method missing scenarios.
- #
class Dynamic < Task
- def initialize(name)
- super(name.to_s, "A dynamically-generated task", name.to_s)
+ def initialize(name, options=nil)
+ super(name.to_s, "A dynamically-generated task", name.to_s, options)
end
def run(instance, args=[])
@@ -25,84 +25,73 @@ class Thor
self.options = other.options.dup if other.options
end
- def short_description
- description.split("\n").first if description
- end
-
# By default, a task invokes a method in the thor class. You can change this
# implementation to create custom tasks.
- #
def run(instance, args=[])
raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance)
instance.send(name, *args)
rescue ArgumentError => e
+ raise e if instance.class.respond_to?(:debugging) && instance.class.debugging
parse_argument_error(instance, e, caller)
rescue NoMethodError => e
+ raise e if instance.class.respond_to?(:debugging) && instance.class.debugging
parse_no_method_error(instance, e)
end
- # Returns the formatted usage. If a class is given, the class arguments are
- # injected in the usage.
- #
- def formatted_usage(klass=nil, namespace=false, show_options=true)
- formatted = if namespace.is_a?(String)
- "#{namespace}:"
- elsif klass && namespace
- "#{klass.namespace.gsub(/^default/,'')}:"
+ # Returns the formatted usage by injecting given required arguments
+ # and required options into the given usage.
+ def formatted_usage(klass, namespace=nil)
+ namespace = klass.namespace if namespace.nil?
+
+ # Add namespace
+ formatted = if namespace
+ "#{namespace.gsub(/^(default|thor:runner:)/,'')}:"
else
""
end
- formatted << formatted_arguments(klass)
- formatted << " #{formatted_options}" if show_options
- formatted.strip!
- formatted
- end
-
- # Injects the class arguments into the task usage.
- #
- def formatted_arguments(klass)
- if klass && !klass.arguments.empty?
+ # Add usage with required arguments
+ formatted << if klass && !klass.arguments.empty?
usage.to_s.gsub(/^#{name}/) do |match|
- match << " " << klass.arguments.map{ |a| a.usage }.join(' ')
+ match << " " << klass.arguments.map{ |a| a.usage }.compact.join(' ')
end
else
usage.to_s
end
- end
- # Returns the options usage for this task.
- #
- def formatted_options
- @formatted_options ||= options.map{ |_, o| o.usage }.sort.join(" ")
+ # Add required options
+ formatted << " #{required_options}"
+
+ # Strip and go!
+ formatted.strip
end
protected
+ def required_options
+ @required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
+ end
+
# Given a target, checks if this class name is not a private/protected method.
- #
def public_method?(instance) #:nodoc:
collection = instance.private_methods + instance.protected_methods
(collection & [name.to_s, name.to_sym]).empty?
end
- # Clean everything that comes from the Thor gempath and remove the caller.
- #
- def sans_backtrace(backtrace, caller) #:nodoc:
- dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/
- saned = backtrace.reject { |frame| frame =~ dirname }
- saned -= caller
+ # For Ruby <= 1.8.7, we have to match the method name that we are trying to call.
+ # In Ruby >= 1.9.1, we have to match the method run in this file.
+ def backtrace_match?(backtrace) #:nodoc:
+ method_name = /`#{Regexp.escape(name.split(':').last)}'/
+ backtrace =~ method_name || backtrace =~ FILE_REGEXP
end
def parse_argument_error(instance, e, caller) #:nodoc:
- backtrace = sans_backtrace(e.backtrace, caller)
-
- if backtrace.empty? && e.message =~ /wrong number of arguments/
+ if e.message =~ /wrong number of arguments/ && backtrace_match?(e.backtrace.first.to_s)
if instance.is_a?(Thor::Group)
raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?"
else
raise InvocationError, "'#{name}' was called incorrectly. Call as " <<
- "'#{formatted_usage(instance.class, true)}'"
+ "'#{formatted_usage(instance.class)}'"
end
else
raise e
diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/util.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/util.rb
index ebae0a3193..c2aed89ccf 100644
--- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/util.rb
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/util.rb
@@ -76,8 +76,10 @@ class Thor
# Returns the thor classes declared inside the given class.
#
def self.thor_classes_in(klass)
+ stringfied_constants = klass.constants.map { |c| c.to_s }
Thor::Base.subclasses.select do |subclass|
- klass.constants.include?(subclass.name.gsub("#{klass.name}::", ''))
+ next unless subclass.name
+ stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ''))
end
end
@@ -155,7 +157,7 @@ class Thor
# inside the sandbox to avoid namespacing conflicts.
#
def self.load_thorfile(path, content=nil)
- content ||= File.read(path)
+ content ||= File.binread(path)
begin
Thor::Sandbox.class_eval(content, path)
@@ -164,26 +166,6 @@ class Thor
end
end
- # Receives a yaml (hash) and updates all constants entries to namespace.
- # This was added to deal with deprecated versions of Thor.
- #
- # TODO Deprecate this method in the future.
- #
- # ==== Returns
- # TrueClass|FalseClass:: Returns true if any change to the yaml file was made.
- #
- def self.convert_constants_to_namespaces(yaml)
- yaml_changed = false
-
- yaml.each do |k, v|
- next unless v[:constants] && v[:namespaces].nil?
- yaml_changed = true
- yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.namespace_from_thor_class(c)}
- end
-
- yaml_changed
- end
-
def self.user_home
@@user_home ||= if ENV["HOME"]
ENV["HOME"]
diff --git a/railties/lib/rails/vendor/thor-0.12.3/lib/thor/version.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/version.rb
new file mode 100644
index 0000000000..3c9dd6f808
--- /dev/null
+++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/version.rb
@@ -0,0 +1,3 @@
+class Thor
+ VERSION = "0.12.3".freeze
+end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 66ab5a08c3..2d6983076a 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -20,7 +20,7 @@ require 'active_support/core_ext/logger'
require 'active_support/test_case'
require 'action_controller'
-require 'rails'
+require 'rails/all'
# TODO: Remove these hacks
class TestApp < Rails::Application
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index ece41f9de8..adb867ca6d 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -16,6 +16,7 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
build_app
boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
end
test "the application root is set correctly" do
@@ -75,10 +76,11 @@ module ApplicationTests
test "the application can be marked as threadsafe when there are no frameworks" do
FileUtils.rm_rf("#{app_path}/config/environments")
add_to_config <<-RUBY
- config.frameworks = []
config.threadsafe!
RUBY
+ use_frameworks []
+
assert_nothing_raised do
require "#{app_path}/config/application"
end
@@ -99,5 +101,21 @@ module ApplicationTests
assert !ActionController.autoload?(:RecordIdentifier)
end
+
+ test "runtime error is raised if config.frameworks= is used" do
+ add_to_config "config.frameworks = []"
+
+ assert_raises RuntimeError do
+ require "#{app_path}/config/environment"
+ end
+ end
+
+ test "runtime error is raised if config.frameworks is used" do
+ add_to_config "config.frameworks -= []"
+
+ assert_raises RuntimeError do
+ require "#{app_path}/config/environment"
+ end
+ end
end
end
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 7b27c780aa..0c858d6394 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -14,7 +14,7 @@ module ApplicationTests
end
def with_config
- require "rails"
+ require "rails/all"
require "rails/generators"
yield app_const.config
end
diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb
index 42fc416faa..3fd0b0e5df 100644
--- a/railties/test/application/initializer_test.rb
+++ b/railties/test/application/initializer_test.rb
@@ -7,7 +7,7 @@ module ApplicationTests
def setup
build_app
boot_rails
- require "rails"
+ FileUtils.rm_rf "#{app_path}/config/environments"
end
test "initializing an application adds the application paths to the load path" do
@@ -51,8 +51,9 @@ module ApplicationTests
assert_nothing_raised NameError do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.frameworks = []
RUBY
+
+ use_frameworks []
require "#{app_path}/config/environment"
end
end
@@ -160,6 +161,7 @@ module ApplicationTests
def setup
build_app
boot_rails
+ FileUtils.rm_rf "#{app_path}/config/environments"
end
test "database middleware doesn't initialize when activerecord is not in frameworks" do
diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb
index 8229e83147..b57e829cca 100644
--- a/railties/test/application/notifications_test.rb
+++ b/railties/test/application/notifications_test.rb
@@ -18,6 +18,7 @@ module ApplicationTests
def setup
build_app
boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
require "active_support/notifications"
@events = []
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 567555fdc5..2cba42551e 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -63,10 +63,11 @@ class AppGeneratorTest < GeneratorsTestCase
assert_no_file "config/database.yml"
end
- def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given
- run_generator ["--skip-activerecord"]
- assert_file "config/application.rb", /config\.frameworks \-= \[ :active_record \]/
- end
+ # TODO: Bring this back using requires
+ # def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given
+ # run_generator ["--skip-activerecord"]
+ # assert_file "config/application.rb", /config\.frameworks \-= \[ :active_record \]/
+ # end
def test_prototype_and_test_unit_are_added_by_default
run_generator
diff --git a/railties/test/initializer/check_ruby_version_test.rb b/railties/test/initializer/check_ruby_version_test.rb
index a2c07ece75..311f19a28a 100644
--- a/railties/test/initializer/check_ruby_version_test.rb
+++ b/railties/test/initializer/check_ruby_version_test.rb
@@ -19,14 +19,14 @@ module InitializerTests
def assert_rails_boots
assert_nothing_raised "It appears that rails does not boot" do
- require "rails"
+ require "rails/all"
end
end
def assert_rails_does_not_boot
$stderr = File.open("/dev/null", "w")
assert_raises(SystemExit) do
- require "rails"
+ require "rails/all"
end
end
end
diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb
index 3bbf9617a0..bfb1887d11 100644
--- a/railties/test/initializer/path_test.rb
+++ b/railties/test/initializer/path_test.rb
@@ -7,14 +7,14 @@ module InitializerTests
def setup
build_app
boot_rails
- require "rails"
+ FileUtils.rm_rf("#{app_path}/config/environments")
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.frameworks = [:action_controller, :action_view, :action_mailer, :active_record]
config.after_initialize do
ActionController::Base.session_store = nil
end
RUBY
+ use_frameworks [:action_controller, :action_view, :action_mailer, :active_record]
require "#{app_path}/config/environment"
@paths = Rails.application.config.paths
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index ee0a812b47..dc5fddb19d 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -207,6 +207,6 @@ Module.new do
`#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}`
File.open("#{tmp_path}/app_template/config/boot.rb", 'w') do |f|
f.puts "require '#{environment}'" if require_environment
- f.puts "require 'rails'"
+ f.puts "require 'rails/all'"
end
end
diff --git a/railties/test/plugins/configuration_test.rb b/railties/test/plugins/configuration_test.rb
index 5786316d1d..25bf24eb3b 100644
--- a/railties/test/plugins/configuration_test.rb
+++ b/railties/test/plugins/configuration_test.rb
@@ -5,27 +5,27 @@ module PluginsTest
def setup
build_app
boot_rails
- require "rails"
+ require "rails/all"
end
test "config is available to plugins" do
- class Foo < Rails::Plugin ; end
+ class Foo < Rails::Railtie ; end
assert_nil Foo.config.action_controller.foo
end
test "a config name is available for the plugin" do
- class Foo < Rails::Plugin ; config.foo.greetings = "hello" ; end
+ class Foo < Rails::Railtie ; config.foo.greetings = "hello" ; end
assert_equal "hello", Foo.config.foo.greetings
end
test "plugin configurations are available in the application" do
- class Foo < Rails::Plugin ; config.foo.greetings = "hello" ; end
+ class Foo < Rails::Railtie ; config.foo.greetings = "hello" ; end
require "#{app_path}/config/application"
assert_equal "hello", AppTemplate::Application.config.foo.greetings
end
test "plugin config merges are deep" do
- class Foo < Rails::Plugin ; config.foo.greetings = 'hello' ; end
+ class Foo < Rails::Railtie ; config.foo.greetings = 'hello' ; end
class MyApp < Rails::Application
config.foo.bar = "bar"
end
diff --git a/railties/test/plugins/framework_extension_test.rb b/railties/test/plugins/framework_extension_test.rb
index a6c7b753f8..c920db77aa 100644
--- a/railties/test/plugins/framework_extension_test.rb
+++ b/railties/test/plugins/framework_extension_test.rb
@@ -7,13 +7,14 @@ module PluginsTest
def setup
build_app
boot_rails
- require "rails"
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ require "rails/all"
end
test "rake_tasks block is executed when MyApp.load_tasks is called" do
$ran_block = false
- class MyPlugin < Rails::Plugin
+ class MyTie < Rails::Railtie
rake_tasks do
$ran_block = true
end
@@ -37,6 +38,7 @@ module PluginsTest
def setup
build_app
boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
end
test "active_record extensions are applied to ActiveRecord" do