aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile3
-rw-r--r--actionmailer/CHANGELOG.md4
-rw-r--r--actionmailer/lib/action_mailer/async.rb41
-rw-r--r--actionmailer/lib/action_mailer/base.rb23
-rw-r--r--actionmailer/lib/action_mailer/collector.rb2
-rw-r--r--actionmailer/test/base_test.rb36
-rw-r--r--actionmailer/test/fixtures/async_mailer/welcome.erb1
-rw-r--r--actionmailer/test/mailers/async_mailer.rb3
-rw-r--r--actionpack/CHANGELOG.md18
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/collector.rb4
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb16
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb6
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb33
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb12
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb31
-rw-r--r--actionpack/lib/action_controller/model_naming.rb12
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb8
-rw-r--r--actionpack/lib/action_controller/test_case.rb65
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb18
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb17
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb14
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb8
-rw-r--r--actionpack/lib/action_view/base.rb17
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb21
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb26
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb3
-rw-r--r--actionpack/test/controller/filters_test.rb12
-rw-r--r--actionpack/test/controller/flash_test.rb27
-rw-r--r--actionpack/test/controller/mime_responds_test.rb4
-rw-r--r--actionpack/test/controller/test_case_test.rb9
-rw-r--r--actionpack/test/dispatch/mapper_test.rb3
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb14
-rw-r--r--actionpack/test/dispatch/routing_test.rb49
-rw-r--r--actionpack/test/fixtures/routes/bogus.rb1
-rw-r--r--actionpack/test/fixtures/routes/external.rb1
-rw-r--r--actionpack/test/template/number_helper_i18n_test.rb122
-rw-r--r--actionpack/test/template/number_helper_test.rb58
-rw-r--r--actionpack/test/template/test_case_test.rb12
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb37
-rw-r--r--activemodel/lib/active_model/callbacks.rb12
-rw-r--r--activemodel/lib/active_model/conversion.rb31
-rw-r--r--activemodel/lib/active_model/dirty.rb6
-rw-r--r--activemodel/lib/active_model/errors.rb191
-rw-r--r--activemodel/lib/active_model/lint.rb45
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb6
-rw-r--r--activemodel/lib/active_model/model.rb53
-rw-r--r--activemodel/lib/active_model/naming.rb149
-rw-r--r--activemodel/lib/active_model/observing.rb3
-rw-r--r--activemodel/lib/active_model/validations.rb3
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb6
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb4
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/format.rb8
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb2
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb2
-rw-r--r--activemodel/lib/active_model/validations/presence.rb4
-rw-r--r--activemodel/lib/active_model/validations/with.rb10
-rw-r--r--activerecord/CHANGELOG.md60
-rw-r--r--activerecord/lib/active_record/associations.rb8
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb31
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb46
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb10
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb14
-rw-r--r--activerecord/lib/active_record/model.rb4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake64
-rw-r--r--activerecord/lib/active_record/relation.rb14
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb29
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb24
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb23
-rw-r--r--activerecord/lib/active_record/store.rb9
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb9
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb4
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/model.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/module.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/enum_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb8
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb1
-rw-r--r--activerecord/test/cases/base_test.rb85
-rw-r--r--activerecord/test/cases/calculations_test.rb11
-rw-r--r--activerecord/test/cases/column_test.rb6
-rw-r--r--activerecord/test/cases/finder_test.rb4
-rw-r--r--activerecord/test/cases/helper.rb1
-rw-r--r--activerecord/test/cases/inclusion_test.rb2
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb18
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb28
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb30
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb21
-rw-r--r--activerecord/test/cases/migration/helper.rb27
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb111
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/query_cache_test.rb8
-rw-r--r--activerecord/test/cases/quoting_test.rb8
-rw-r--r--activerecord/test/cases/relations_test.rb21
-rw-r--r--activerecord/test/cases/serialization_test.rb7
-rw-r--r--activerecord/test/cases/store_test.rb12
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb11
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb18
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb26
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb21
-rw-r--r--activerecord/test/models/developer.rb1
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb9
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb10
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activesupport/CHANGELOG.md6
-rw-r--r--activesupport/activesupport.gemspec1
-rw-r--r--activesupport/lib/active_support/callbacks.rb3
-rw-r--r--activesupport/lib/active_support/configurable.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb26
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb14
-rw-r--r--activesupport/lib/active_support/inflections.rb2
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb2
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb1
-rw-r--r--activesupport/lib/active_support/number_helper.rb29
-rw-r--r--activesupport/lib/active_support/test_case.rb5
-rw-r--r--activesupport/lib/active_support/testing/mocha_module.rb22
-rw-r--r--activesupport/lib/active_support/testing/mochaing.rb7
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb36
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb3
-rw-r--r--activesupport/test/abstract_unit.rb2
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb1
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb31
-rw-r--r--activesupport/test/file_update_checker_test.rb15
-rw-r--r--activesupport/test/i18n_test.rb5
-rw-r--r--activesupport/test/number_helper_i18n_test.rb114
-rw-r--r--guides/code/getting_started/test/test_helper.rb2
-rw-r--r--guides/source/4_0_release_notes.textile260
-rw-r--r--guides/source/action_mailer_basics.textile31
-rw-r--r--guides/source/configuring.textile2
-rw-r--r--guides/source/debugging_rails_applications.textile2
-rw-r--r--guides/source/getting_started.textile16
-rw-r--r--guides/source/performance_testing.textile6
-rw-r--r--guides/source/routing.textile26
-rw-r--r--railties/CHANGELOG.md13
-rw-r--r--railties/lib/rails/application.rb191
-rw-r--r--railties/lib/rails/application/railties.rb13
-rw-r--r--railties/lib/rails/application/routes_inspector.rb (renamed from railties/lib/rails/application/route_inspector.rb)2
-rw-r--r--railties/lib/rails/application/routes_reloader.rb13
-rw-r--r--railties/lib/rails/engine.rb140
-rw-r--r--railties/lib/rails/engine/configuration.rb1
-rw-r--r--railties/lib/rails/engine/railties.rb26
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE49
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/gitignore2
-rw-r--r--railties/lib/rails/info_controller.rb4
-rw-r--r--railties/lib/rails/paths.rb13
-rw-r--r--railties/lib/rails/queueing.rb7
-rw-r--r--railties/lib/rails/railtie.rb32
-rw-r--r--railties/lib/rails/tasks/routes.rake4
-rw-r--r--railties/lib/rails/test_help.rb5
-rw-r--r--railties/railties.gemspec2
-rw-r--r--railties/test/application/paths_test.rb2
-rw-r--r--railties/test/application/queue_test.rb85
-rw-r--r--railties/test/application/rake_test.rb18
-rw-r--r--railties/test/application/routes_inspect_test.rb (renamed from railties/test/application/route_inspect_test.rb)6
-rw-r--r--railties/test/application/routing_test.rb83
-rw-r--r--railties/test/engine_test.rb10
-rw-r--r--railties/test/generators/generated_attribute_test.rb23
-rw-r--r--railties/test/generators/model_generator_test.rb17
-rw-r--r--railties/test/generators/namespaced_generators_test.rb2
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb8
-rw-r--r--railties/test/generators_test.rb1
-rw-r--r--railties/test/isolation/abstract_unit.rb16
-rw-r--r--railties/test/paths_test.rb9
-rw-r--r--railties/test/queueing/test_queue_test.rb85
-rw-r--r--railties/test/railties/engine_test.rb2
-rw-r--r--railties/test/railties/generators_test.rb12
190 files changed, 2595 insertions, 1358 deletions
diff --git a/Gemfile b/Gemfile
index 4e34d42fb6..1c378c7a68 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,8 +8,7 @@ else
gem 'arel'
end
-gem 'minitest', '~> 3.1.0'
-gem 'mocha', '>= 0.11.2'
+gem 'mocha', '>= 0.11.2', :require => false
gem 'rack-test', github: "brynary/rack-test"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index a822412090..96cfb43e0b 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,7 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* Allow to set default Action Mailer options via `config.action_mailer.default_options=` *Robert Pankowecki*
+
* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu*
+* Asynchronously send messages via the Rails Queue *Brian Cardarella*
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
diff --git a/actionmailer/lib/action_mailer/async.rb b/actionmailer/lib/action_mailer/async.rb
new file mode 100644
index 0000000000..a364342745
--- /dev/null
+++ b/actionmailer/lib/action_mailer/async.rb
@@ -0,0 +1,41 @@
+require 'delegate'
+
+module ActionMailer
+ module Async
+ def method_missing(method_name, *args)
+ if action_methods.include?(method_name.to_s)
+ QueuedMessage.new(queue, self, method_name, *args)
+ else
+ super
+ end
+ end
+
+ def queue
+ Rails.queue
+ end
+
+ class QueuedMessage < ::Delegator
+ attr_reader :queue
+
+ def initialize(queue, mailer_class, method_name, *args)
+ @queue = queue
+ @mailer_class = mailer_class
+ @method_name = method_name
+ @args = args
+ end
+
+ def __getobj__
+ @actual_message ||= @mailer_class.send(:new, @method_name, *@args).message
+ end
+
+ def run
+ __getobj__.deliver
+ end
+
+ # Will push the message onto the Queue to be processed
+ def deliver
+ @queue << self
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 739f9a52a9..150d435140 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -278,6 +278,11 @@ module ActionMailer #:nodoc:
# set something in the defaults using a proc, and then set the same thing inside of your
# mailer method, it will get over written by the mailer method.
#
+ # It is also possible to set these default options that will be used in all mailers through
+ # the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>:
+ #
+ # config.action_mailer.default_options = { from: "no-reply@example.org" }
+ #
# = Callbacks
#
# You can specify callbacks using before_filter and after_filter for configuring your messages.
@@ -421,6 +426,10 @@ module ActionMailer #:nodoc:
self.default_params = default_params.merge(value).freeze if value
default_params
end
+ # Allows to set defaults through app configuration:
+ #
+ # config.action_mailer.default_options = { from: "no-reply@example.org" }
+ alias :default_options= :default
# Receives a raw email, parses it into an email object, decodes it,
# instantiates a new mailer, and passes the email object to the mailer
@@ -456,6 +465,19 @@ module ActionMailer #:nodoc:
super || action_methods.include?(method.to_s)
end
+ # Will force ActionMailer to push new messages to the queue defined
+ # in the ActionMailer class when set to true.
+ #
+ # class WelcomeMailer < ActionMailer::Base
+ # self.async = true
+ # end
+ def async=(truth)
+ if truth
+ require 'action_mailer/async'
+ extend ActionMailer::Async
+ end
+ end
+
protected
def set_payload_for_mail(payload, mail) #:nodoc:
@@ -774,4 +796,3 @@ module ActionMailer #:nodoc:
ActiveSupport.run_load_hooks(:action_mailer, self)
end
end
-
diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb
index 17b22aea2a..b8d1db9558 100644
--- a/actionmailer/lib/action_mailer/collector.rb
+++ b/actionmailer/lib/action_mailer/collector.rb
@@ -15,7 +15,7 @@ module ActionMailer #:nodoc:
def any(*args, &block)
options = args.extract_options!
- raise "You have to supply at least one format" if args.empty?
+ raise ArgumentError, "You have to supply at least one format" if args.empty?
args.each { |type| send(type, options.dup, &block) }
end
alias :all :any
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 1b2e39b3f7..4ed332d13d 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -7,6 +7,8 @@ require 'active_support/time'
require 'mailers/base_mailer'
require 'mailers/proc_mailer'
require 'mailers/asset_mailer'
+require 'mailers/async_mailer'
+require 'rails/queueing'
class BaseTest < ActiveSupport::TestCase
def teardown
@@ -419,6 +421,26 @@ class BaseTest < ActiveSupport::TestCase
assert_equal(1, BaseMailer.deliveries.length)
end
+ def stub_queue(klass, queue)
+ Class.new(klass) {
+ extend Module.new {
+ define_method :queue do
+ queue
+ end
+ }
+ }
+ end
+
+ test "delivering message asynchronously" do
+ testing_queue = Rails::Queueing::TestQueue.new
+ AsyncMailer.delivery_method = :test
+ AsyncMailer.deliveries.clear
+ stub_queue(AsyncMailer, testing_queue).welcome.deliver
+ assert_equal(0, AsyncMailer.deliveries.length)
+ testing_queue.drain
+ assert_equal(1, AsyncMailer.deliveries.length)
+ end
+
test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do
mail = Mail::Message.new
mail.expects(:do_delivery).once
@@ -434,6 +456,7 @@ class BaseTest < ActiveSupport::TestCase
end
test "should raise if missing template in implicit render" do
+ BaseMailer.deliveries.clear
assert_raises ActionView::MissingTemplate do
BaseMailer.implicit_different_template('missing_template').deliver
end
@@ -630,6 +653,19 @@ class BaseTest < ActiveSupport::TestCase
assert_equal "Anonymous mailer body", mailer.welcome.body.encoded.strip
end
+ test "default_from can be set" do
+ class DefaultFromMailer < ActionMailer::Base
+ default :to => 'system@test.lindsaar.net'
+ self.default_options = {from: "robert.pankowecki@gmail.com"}
+
+ def welcome
+ mail(subject: "subject", body: "hello world")
+ end
+ end
+
+ assert_equal ["robert.pankowecki@gmail.com"], DefaultFromMailer.welcome.from
+ end
+
protected
# Execute the block setting the given values and restoring old values after
diff --git a/actionmailer/test/fixtures/async_mailer/welcome.erb b/actionmailer/test/fixtures/async_mailer/welcome.erb
new file mode 100644
index 0000000000..01f3f00c63
--- /dev/null
+++ b/actionmailer/test/fixtures/async_mailer/welcome.erb
@@ -0,0 +1 @@
+Welcome \ No newline at end of file
diff --git a/actionmailer/test/mailers/async_mailer.rb b/actionmailer/test/mailers/async_mailer.rb
new file mode 100644
index 0000000000..ce601e7343
--- /dev/null
+++ b/actionmailer/test/mailers/async_mailer.rb
@@ -0,0 +1,3 @@
+class AsyncMailer < BaseMailer
+ self.async = true
+end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 345b5aa330..67c9e04ab2 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,17 @@
## Rails 4.0.0 (unreleased) ##
+* Add `ActionController::Flash.add_flash_types` method to allow people to register their own flash types. e.g.:
+
+ class ApplicationController
+ add_flash_types :error, :warning
+ end
+
+ If you add the above code, you can use `<%= error %>` in an erb, and `rediect_to /foo, :error => 'message'` in a controller.
+
+ *kennyj*
+
+* Remove Active Model dependency from Action Pack. *Guillermo Iguaran*
+
* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping:
get Rack::Utils.escape('こんにちは') => 'home#index'
@@ -12,9 +24,9 @@
* Return proper format on exceptions. *Santiago Pastorino*
-* Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki*
+* Allow to use `mounted_helpers` (helpers for accessing mounted engines) in `ActionView::TestCase`. *Piotr Sarnacki*
-* Include mounted_helpers (helpers for accessing mounted engines) in ActionDispatch::IntegrationTest by default. *Piotr Sarnacki*
+* Include `mounted_helpers` (helpers for accessing mounted engines) in `ActionDispatch::IntegrationTest` by default. *Piotr Sarnacki*
* Extracted redirect logic from `ActionController::ForceSSL::ClassMethods.force_ssl` into `ActionController::ForceSSL#force_ssl_redirect`
@@ -47,7 +59,7 @@
*Piotr Sarnacki*
-* `truncate` now always returns an escaped HTMl-safe string. The option `:escape` can be used as
+* `truncate` now always returns an escaped HTML-safe string. The option `:escape` can be used as
false to not escape the result.
*Li Ellis Gallardo + Rafael Mendonça França*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 938f3d6dc2..6075e2f02b 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -18,7 +18,6 @@ Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency('activesupport', version)
- s.add_dependency('activemodel', version)
s.add_dependency('rack-cache', '~> 1.2')
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('rack', '~> 1.4.1')
@@ -26,5 +25,6 @@ Gem::Specification.new do |s|
s.add_dependency('journey', '~> 1.0.1')
s.add_dependency('erubis', '~> 2.7.0')
+ s.add_development_dependency('activemodel', version)
s.add_development_dependency('tzinfo', '~> 0.3.33')
end
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index 492329c401..09b9e7ddf0 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -16,6 +16,10 @@ module AbstractController
generate_method_for_mime(mime)
end
+ Mime::Type.register_callback do |mime|
+ generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym)
+ end
+
protected
def method_missing(symbol, &block)
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 7d73c6af8d..3da2834af0 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -49,9 +49,19 @@ module AbstractController
module ClassMethods
def view_context_class
@view_context_class ||= begin
- routes = _routes if respond_to?(:_routes)
- helpers = _helpers if respond_to?(:_helpers)
- ActionView::Base.prepare(routes, helpers)
+ routes = respond_to?(:_routes) && _routes
+ helpers = respond_to?(:_helpers) && _helpers
+
+ Class.new(ActionView::Base) do
+ if routes
+ include routes.url_helpers
+ include routes.mounted_helpers
+ end
+
+ if helpers
+ include helpers
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index cc1fa23935..39da15e26a 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -72,6 +72,12 @@ module ActionController #:nodoc:
self.controller = nil
end
+ def around(controller)
+ before(controller)
+ yield
+ after(controller)
+ end
+
protected
# gets the action cache path for the given options.
def action_path_for(options)
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index bd768b634e..b078beb675 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -3,19 +3,34 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- delegate :flash, :to => :request
- delegate :alert, :notice, :to => "request.flash"
- helper_method :alert, :notice
+ class_attribute :_flash_types, instance_accessor: false
+ self._flash_types = []
+
+ delegate :flash, to: :request
+ add_flash_types(:alert, :notice)
end
- protected
- def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
- if alert = response_status_and_flash.delete(:alert)
- flash[:alert] = alert
+ module ClassMethods
+ def add_flash_types(*types)
+ types.each do |type|
+ next if _flash_types.include?(type)
+
+ define_method(type) do
+ request.flash[type]
+ end
+ helper_method type
+
+ _flash_types << type
end
+ end
+ end
- if notice = response_status_and_flash.delete(:notice)
- flash[:notice] = notice
+ protected
+ def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
+ self.class._flash_types.each do |flash_type|
+ if type = response_status_and_flash.delete(flash_type)
+ flash[flash_type] = type
+ end
end
if other_flashes = response_status_and_flash.delete(:flash)
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index 544b4989c7..bdf6e88699 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -8,9 +8,8 @@ module ActionController
delegate :headers, :status=, :location=, :content_type=,
:status, :location, :content_type, :to => "@_response"
- def dispatch(action, request, response = ActionDispatch::Response.new)
- @_response ||= response
- @_response.request ||= request
+ def dispatch(action, request)
+ set_response!(request)
super(action, request)
end
@@ -22,5 +21,12 @@ module ActionController
def reset_session
@_request.reset_session
end
+
+ private
+
+ def set_response!(request)
+ @_response = ActionDispatch::Response.new
+ @_response.request = request
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index d1813ee745..0377b8c4cf 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -4,30 +4,25 @@ module ActionController
include RackDelegation
- def recycle!
- @_url_options = nil
- end
-
-
- # TODO: Clean this up
- def process_with_new_base_test(request, response)
- @_request = request
- @_response = response
- @_response.request = request
- ret = process(request.parameters[:action])
- if cookies = @_request.env['action_dispatch.cookies']
- cookies.write(@_response)
- end
- @_response.prepare!
- ret
- end
-
# TODO : Rewrite tests using controller.headers= to use Rack env
def headers=(new_headers)
@_response ||= ActionDispatch::Response.new
@_response.headers.replace(new_headers)
end
+ # Behavior specific to functional tests
+ module Functional # :nodoc:
+ def set_response!(request)
+ end
+
+ def recycle!
+ @_url_options = nil
+ self.response_body = nil
+ self.formats = nil
+ self.params = nil
+ end
+ end
+
module ClassMethods
def before_filters
_process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
diff --git a/actionpack/lib/action_controller/model_naming.rb b/actionpack/lib/action_controller/model_naming.rb
new file mode 100644
index 0000000000..785221dc3d
--- /dev/null
+++ b/actionpack/lib/action_controller/model_naming.rb
@@ -0,0 +1,12 @@
+module ActionController
+ module ModelNaming
+ # Converts the given object to an ActiveModel compliant one.
+ def convert_to_model(object)
+ object.respond_to?(:to_model) ? object.to_model : object
+ end
+
+ def model_name_from_record_or_class(record_or_class)
+ (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 16a5decc62..d3ac406618 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module'
+require 'action_controller/model_naming'
module ActionController
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
@@ -27,6 +28,8 @@ module ActionController
module RecordIdentifier
extend self
+ include ModelNaming
+
JOIN = '_'.freeze
NEW = 'new'.freeze
@@ -40,7 +43,7 @@ module ActionController
# dom_class(post, :edit) # => "edit_post"
# dom_class(Person, :edit) # => "edit_person"
def dom_class(record_or_class, prefix = nil)
- singular = ActiveModel::Naming.param_key(record_or_class)
+ singular = model_name_from_record_or_class(record_or_class).param_key
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
end
@@ -73,8 +76,7 @@ module ActionController
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
# make sure yourself that your dom ids are valid, in case you overwrite this method.
def record_key_for_dom_id(record)
- record = record.to_model if record.respond_to?(:to_model)
- key = record.to_key
+ key = convert_to_model(record).to_key
key ? key.join('_') : key
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index a1f29ea1bc..0498b9d138 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -143,6 +143,9 @@ module ActionController
end
class TestRequest < ActionDispatch::TestRequest #:nodoc:
+ DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
+ DEFAULT_ENV.delete 'PATH_INFO'
+
def initialize(env = {})
super
@@ -150,10 +153,6 @@ module ActionController
self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
end
- class Result < ::Array #:nodoc:
- def to_s() join '/' end
- end
-
def assign_parameters(routes, controller_path, action, parameters = {})
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
extra_keys = routes.extra_keys(parameters)
@@ -171,7 +170,7 @@ module ActionController
non_path_parameters[key] = value
else
if value.is_a?(Array)
- value = Result.new(value.map(&:to_param))
+ value = value.map(&:to_param)
else
value = value.to_param
end
@@ -211,6 +210,12 @@ module ActionController
cookie_jar.update(@set_cookies)
cookie_jar.recycle!
end
+
+ private
+
+ def default_env
+ DEFAULT_ENV
+ end
end
class TestResponse < ActionDispatch::TestResponse
@@ -430,8 +435,13 @@ module ActionController
end
# Executes a request simulating HEAD HTTP method and set/volley the response
- def head(action, parameters = nil, session = nil, flash = nil)
- process(action, "HEAD", parameters, session, flash)
+ def head(action, *args)
+ process(action, "HEAD", *args)
+ end
+
+ # Executes a request simulating OPTIONS HTTP method and set/volley the response
+ def options(action, *args)
+ process(action, "OPTIONS", *args)
end
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@@ -471,13 +481,17 @@ module ActionController
# proper params, as is the case when engaging rack.
parameters = paramify_values(parameters) if html_format?(parameters)
+ @html_document = nil
+
+ unless @controller.respond_to?(:recycle!)
+ @controller.extend(Testing::Functional)
+ @controller.class.class_eval { include Testing }
+ end
+
@request.recycle!
@response.recycle!
- @controller.response_body = nil
- @controller.formats = nil
- @controller.params = nil
+ @controller.recycle!
- @html_document = nil
@request.env['REQUEST_METHOD'] = http_method
parameters ||= {}
@@ -490,26 +504,34 @@ module ActionController
@request.session.update(session) if session
@request.session["flash"] = @request.flash.update(flash || {})
- @controller.request = @request
+ @controller.request = @request
+ @controller.response = @response
+
build_request_uri(action, parameters)
- @controller.class.class_eval { include Testing }
- @controller.recycle!
- @controller.process_with_new_base_test(@request, @response)
+
+ name = @request.parameters[:action]
+
+ @controller.process(name)
+
+ if cookies = @request.env['action_dispatch.cookies']
+ cookies.write(@response)
+ end
+ @response.prepare!
+
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
@request.session.delete('flash') if @request.session['flash'].blank?
@response
end
def setup_controller_request_and_response
- @request = TestRequest.new
- @response = TestResponse.new
+ @request = TestRequest.new
+ @response = TestResponse.new
+ @response.request = @request
if klass = self.class.controller_class
@controller ||= klass.new rescue nil
end
- @request.env.delete('PATH_INFO')
-
if defined?(@controller) && @controller
@controller.request = @request
@controller.params = {}
@@ -523,7 +545,7 @@ module ActionController
setup :setup_controller_request_and_response
end
- private
+ private
def check_required_ivars
# Sanity check for required instance variables so we can give an
# understandable error message.
@@ -564,8 +586,7 @@ module ActionController
def html_format?(parameters)
return true unless parameters.is_a?(Hash)
- format = Mime[parameters[:format]]
- format.nil? || format.html?
+ Mime.fetch(parameters[:format]) { Mime['html'] }.html?
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index ee1913dbf9..fe39c220a5 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -29,6 +29,11 @@ module Mime
Type.lookup_by_extension(type.to_s)
end
+ def self.fetch(type)
+ return type if type.is_a?(Type)
+ EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
+ end
+
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
#
# class PostsController < ActionController::Base
@@ -53,6 +58,8 @@ module Mime
cattr_reader :browser_generated_types
attr_reader :symbol
+ @register_callbacks = []
+
# A simple helper class used in parsing the accept header
class AcceptItem #:nodoc:
attr_accessor :order, :name, :q
@@ -84,6 +91,10 @@ module Mime
TRAILING_STAR_REGEXP = /(text|application)\/\*/
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
+ def register_callback(&block)
+ @register_callbacks << block
+ end
+
def lookup(string)
LOOKUP[string]
end
@@ -101,10 +112,15 @@ module Mime
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
- SET << Mime.const_get(symbol.upcase)
+ new_mime = Mime.const_get(symbol.upcase)
+ SET << new_mime
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
+
+ @register_callbacks.each do |callback|
+ callback.call(new_mime)
+ end
end
def parse(accept_header)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 94242ad962..53a4afecb3 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1316,22 +1316,6 @@ module ActionDispatch
parent_resource.instance_of?(Resource) && @scope[:shallow]
end
- def draw(name)
- path = @draw_paths.find do |_path|
- File.exists? "#{_path}/#{name}.rb"
- end
-
- unless path
- msg = "Your router tried to #draw the external file #{name}.rb,\n" \
- "but the file was not found in:\n\n"
- msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
- raise ArgumentError, msg
- end
-
- route_path = "#{path}/#{name}.rb"
- instance_eval(File.read(route_path), route_path.to_s)
- end
-
# match 'path' => 'controller#action'
# match 'path', to: 'controller#action'
# match 'path', 'otherpath', on: :member, via: :get
@@ -1581,7 +1565,6 @@ module ActionDispatch
def initialize(set) #:nodoc:
@set = set
- @draw_paths = set.draw_paths
@scope = { :path_names => @set.resources_path_names }
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 86ce7a83b9..3d7b8878b8 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -1,3 +1,5 @@
+require 'action_controller/model_naming'
+
module ActionDispatch
module Routing
# Polymorphic URL helpers are methods for smart resolution to a named route call when
@@ -53,6 +55,8 @@ module ActionDispatch
# form_for([blog, @post]) # => "/blog/posts/1"
#
module PolymorphicRoutes
+ include ActionController::ModelNaming
+
# Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example:
#
@@ -154,10 +158,6 @@ module ActionDispatch
options[:action] ? "#{options[:action]}_" : ''
end
- def convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
- end
-
def routing_type(options)
options[:routing_type] || :url
end
@@ -169,7 +169,7 @@ module ActionDispatch
if parent.is_a?(Symbol) || parent.is_a?(String)
parent
else
- ActiveModel::Naming.singular_route_key(parent)
+ model_name_from_record_or_class(parent).singular_route_key
end
end
else
@@ -181,9 +181,9 @@ module ActionDispatch
route << record
elsif record
if inflection == :singular
- route << ActiveModel::Naming.singular_route_key(record)
+ route << model_name_from_record_or_class(record).singular_route_key
else
- route << ActiveModel::Naming.route_key(record)
+ route << model_name_from_record_or_class(record).route_key
end
else
raise ArgumentError, "Nil location provided. Can't build URI."
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 64b1d58ae9..1d6ca0c78d 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -237,7 +237,6 @@ module ActionDispatch
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
- attr_accessor :draw_paths
alias :routes :set
@@ -249,7 +248,6 @@ module ActionDispatch
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
- self.draw_paths = []
self.request_class = request_class
@valid_conditions = { :controller => true, :action => true }
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index a86b510719..639ae6f398 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -12,7 +12,7 @@ module ActionDispatch
def initialize(env = {})
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
- super(DEFAULT_ENV.merge(env))
+ super(default_env.merge(env))
self.host = 'test.host'
self.remote_addr = '0.0.0.0'
@@ -69,5 +69,11 @@ module ActionDispatch
def cookies
@cookies ||= {}.with_indifferent_access
end
+
+ private
+
+ def default_env
+ DEFAULT_ENV
+ end
end
end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index f98648d930..7bfbc1f0aa 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -148,7 +148,6 @@ module ActionView #:nodoc:
cattr_accessor :prefix_partial_path_with_controller_namespace
@@prefix_partial_path_with_controller_namespace = true
- class_attribute :helpers
class_attribute :_routes
class_attribute :logger
@@ -166,22 +165,6 @@ module ActionView #:nodoc:
def xss_safe? #:nodoc:
true
end
-
- # This method receives routes and helpers from the controller
- # and return a subclass ready to be used as view context.
- def prepare(routes, helpers) #:nodoc:
- Class.new(self) do
- if routes
- include routes.url_helpers
- include routes.mounted_helpers
- end
-
- if helpers
- include helpers
- self.helpers = helpers
- end
- end
- end
end
attr_accessor :view_renderer
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index ba2a26fd09..b34f6c8650 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -12,6 +12,7 @@ require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/array/extract_options'
require 'active_support/deprecation'
require 'active_support/core_ext/string/inflections'
+require 'action_controller/model_naming'
module ActionView
# = Action View Form Helpers
@@ -117,11 +118,7 @@ module ActionView
include FormTagHelper
include UrlHelper
-
- # Converts the given object to an ActiveModel compliant one.
- def convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
- end
+ include ActionController::ModelNaming
# Creates a form that allows the user to create or update the attributes
# of a specific model object.
@@ -411,7 +408,7 @@ module ActionView
object = nil
else
object = record.is_a?(Array) ? record.last : record
- object_name = options[:as] || ActiveModel::Naming.param_key(object)
+ object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
@@ -1128,7 +1125,7 @@ module ActionView
object_name = record_name
else
object = record_name
- object_name = ActiveModel::Naming.param_key(object)
+ object_name = model_name_from_record_or_class(object).param_key
end
builder = options[:builder] || default_form_builder
@@ -1142,9 +1139,11 @@ module ActionView
end
class FormBuilder
+ include ActionController::ModelNaming
+
# The methods which wrap a form helper call.
class_attribute :field_helpers
- self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model]
+ self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model, :model_name_from_record_or_class]
attr_accessor :object_name, :object, :options
@@ -1214,7 +1213,7 @@ module ActionView
end
else
record_object = record_name.is_a?(Array) ? record_name.last : record_name
- record_name = ActiveModel::Naming.param_key(record_object)
+ record_name = model_name_from_record_or_class(record_object).param_key
end
index = if options.has_key?(:index)
@@ -1396,10 +1395,6 @@ module ActionView
@nested_child_index[name] ||= -1
@nested_child_index[name] += 1
end
-
- def convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
- end
end
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 8f97d1f014..9720e90429 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -59,7 +59,7 @@ module ActionView
return unless number
options = options.symbolize_keys
- parse_float(number, true) if options[:raise]
+ parse_float(number, true) if options.delete(:raise)
ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
end
@@ -109,7 +109,9 @@ module ActionView
return unless number
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_currency(number, options) }
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
+ ActiveSupport::NumberHelper.number_to_currency(number, options)
+ }
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -152,7 +154,9 @@ module ActionView
return unless number
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_percentage(number, options) }
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
+ ActiveSupport::NumberHelper.number_to_percentage(number, options)
+ }
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -187,7 +191,9 @@ module ActionView
def number_with_delimiter(number, options = {})
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_delimited(number, options) }
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
+ ActiveSupport::NumberHelper.number_to_delimited(number, options)
+ }
end
# Formats a +number+ with the specified level of
@@ -234,7 +240,9 @@ module ActionView
def number_with_precision(number, options = {})
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_rounded(number, options) }
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
+ ActiveSupport::NumberHelper.number_to_rounded(number, options)
+ }
end
@@ -288,7 +296,9 @@ module ActionView
def number_to_human_size(number, options = {})
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human_size(number, options) }
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
+ ActiveSupport::NumberHelper.number_to_human_size(number, options)
+ }
end
# Pretty prints (formats and approximates) a number in a way it
@@ -392,7 +402,9 @@ module ActionView
def number_to_human(number, options = {})
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human(number, options) }
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
+ ActiveSupport::NumberHelper.number_to_human(number, options)
+ }
end
private
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index 72616b7463..e3d8e9d508 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -1,7 +1,6 @@
module ActionView
class AbstractRenderer #:nodoc:
- delegate :find_template, :template_exists?, :with_fallbacks, :update_details,
- :with_layout_format, :formats, :to => :@lookup_context
+ delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index ef7fbca675..b9cb93f0f4 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -326,6 +326,12 @@ class FilterTest < ActionController::TestCase
controller.instance_variable_set(:"@after_ran", true)
controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
end
+
+ def around(controller)
+ before(controller)
+ yield
+ after(controller)
+ end
end
class AppendedAroundFilter
@@ -336,6 +342,12 @@ class FilterTest < ActionController::TestCase
def after(controller)
controller.class.execution_log << " after appended aroundfilter "
end
+
+ def around(controller)
+ before(controller)
+ yield
+ after(controller)
+ end
end
class AuditController < ActionController::Base
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index e4b34125ad..8340aab4d2 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -90,6 +90,10 @@ class FlashTest < ActionController::TestCase
def redirect_with_other_flashes
redirect_to '/wonderland', :flash => { :joyride => "Horses!" }
end
+
+ def redirect_with_foo_flash
+ redirect_to "/wonderland", :foo => 'for great justice'
+ end
end
tests TestController
@@ -203,6 +207,12 @@ class FlashTest < ActionController::TestCase
get :redirect_with_other_flashes
assert_equal "Horses!", @controller.send(:flash)[:joyride]
end
+
+ def test_redirect_to_with_adding_flash_types
+ @controller.class.add_flash_types :foo
+ get :redirect_with_foo_flash
+ assert_equal "for great justice", @controller.send(:flash)[:foo]
+ end
end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
@@ -210,9 +220,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
class TestController < ActionController::Base
- def dont_set_flash
- head :ok
- end
+ add_flash_types :bar
def set_flash
flash["that"] = "hello"
@@ -227,6 +235,11 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
def use_flash
render :inline => "flash: #{flash["that"]}"
end
+
+ def set_bar
+ flash[:bar] = "for great justice"
+ head :ok
+ end
end
def test_flash
@@ -266,6 +279,14 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ def test_added_flash_types_method
+ with_test_route_set do
+ get '/set_bar'
+ assert_response :success
+ assert_equal 'for great justice', @controller.bar
+ end
+ end
+
private
# Overwrite get to send SessionSecret in env hash
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index bdcd5561a8..c8e036b116 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -505,7 +505,7 @@ class RespondToControllerTest < ActionController::TestCase
end
class RespondWithController < ActionController::Base
- respond_to :html, :json
+ respond_to :html, :json, :touch
respond_to :xml, :except => :using_resource_with_block
respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ]
@@ -623,12 +623,14 @@ class RespondWithControllerTest < ActionController::TestCase
super
@request.host = "www.example.com"
Mime::Type.register_alias('text/html', :iphone)
+ Mime::Type.register_alias('text/html', :touch)
Mime::Type.register('text/x-mobile', :mobile)
end
def teardown
super
Mime::Type.unregister(:iphone)
+ Mime::Type.unregister(:touch)
Mime::Type.unregister(:mobile)
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 49137946fe..8990fc34d6 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -197,6 +197,11 @@ XML
assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 }
end
+ def test_options
+ options :test_params
+ assert_equal 200, @response.status
+ end
+
def test_process_without_flash
process :set_flash
assert_equal '><', flash['test']
@@ -635,7 +640,7 @@ XML
get :test_params, :path => ['hello', 'world']
assert_equal ['hello', 'world'], @request.path_parameters['path']
- assert_equal 'hello/world', @request.path_parameters['path'].to_s
+ assert_equal 'hello/world', @request.path_parameters['path'].to_param
end
end
@@ -913,4 +918,4 @@ class AnonymousControllerTest < ActionController::TestCase
get :index
assert_equal 'anonymous', @response.body
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index bd078d2b21..8070bdec8a 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -4,12 +4,11 @@ module ActionDispatch
module Routing
class MapperTest < ActiveSupport::TestCase
class FakeSet
- attr_reader :routes, :draw_paths
+ attr_reader :routes
alias :set :routes
def initialize
@routes = []
- @draw_paths = []
end
def resources_path_names
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 9d77c3acc5..ed012093a7 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -118,6 +118,20 @@ class MimeTypeTest < ActiveSupport::TestCase
end
end
+ test "register callbacks" do
+ begin
+ registered_mimes = []
+ Mime::Type.register_callback do |mime|
+ registered_mimes << mime
+ end
+
+ Mime::Type.register("text/foo", :foo)
+ assert_equal registered_mimes, [Mime::FOO]
+ ensure
+ Mime::Type.unregister(:FOO)
+ end
+ end
+
test "custom type with extension aliases" do
begin
Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"]
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index f15dd7214b..fed26d89f8 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2324,55 +2324,6 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
end
end
-class TestDrawExternalFile < ActionDispatch::IntegrationTest
- class ExternalController < ActionController::Base
- def index
- render :text => "external#index"
- end
- end
-
- DRAW_PATH = File.expand_path('../../fixtures/routes', __FILE__)
-
- DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new.tap do |app|
- app.draw_paths << DRAW_PATH
- end
-
- def app
- DefaultScopeRoutes
- end
-
- def test_draw_external_file
- DefaultScopeRoutes.draw do
- scope :module => 'test_draw_external_file' do
- draw :external
- end
- end
-
- get '/external'
- assert_equal "external#index", @response.body
- end
-
- def test_draw_nonexistent_file
- exception = assert_raise ArgumentError do
- DefaultScopeRoutes.draw do
- draw :nonexistent
- end
- end
- assert_match 'Your router tried to #draw the external file nonexistent.rb', exception.message
- assert_match DRAW_PATH.to_s, exception.message
- end
-
- def test_draw_bogus_file
- exception = assert_raise NoMethodError do
- DefaultScopeRoutes.draw do
- draw :bogus
- end
- end
- assert_match "undefined method `wrong'", exception.message
- assert_match 'test/fixtures/routes/bogus.rb:1', exception.backtrace.first
- end
-end
-
class TestDefaultScope < ActionDispatch::IntegrationTest
module ::Blog
class PostsController < ActionController::Base
diff --git a/actionpack/test/fixtures/routes/bogus.rb b/actionpack/test/fixtures/routes/bogus.rb
deleted file mode 100644
index 41fbf0cd64..0000000000
--- a/actionpack/test/fixtures/routes/bogus.rb
+++ /dev/null
@@ -1 +0,0 @@
-wrong :route
diff --git a/actionpack/test/fixtures/routes/external.rb b/actionpack/test/fixtures/routes/external.rb
deleted file mode 100644
index d103c39f53..0000000000
--- a/actionpack/test/fixtures/routes/external.rb
+++ /dev/null
@@ -1 +0,0 @@
-get '/external' => 'external#index'
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
deleted file mode 100644
index d6e9de9555..0000000000
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'abstract_unit'
-
-class NumberHelperTest < ActionView::TestCase
- tests ActionView::Helpers::NumberHelper
-
- def setup
- I18n.backend.store_translations 'ts',
- :number => {
- :format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false },
- :currency => { :format => { :unit => '&$', :format => '%u - %n', :negative_format => '(%u - %n)', :precision => 2 } },
- :human => {
- :format => {
- :precision => 2,
- :significant => true,
- :strip_insignificant_zeros => true
- },
- :storage_units => {
- :format => "%n %u",
- :units => {
- :byte => "b",
- :kb => "k"
- }
- },
- :decimal_units => {
- :format => "%n %u",
- :units => {
- :deci => {:one => "Tenth", :other => "Tenths"},
- :unit => "u",
- :ten => {:one => "Ten", :other => "Tens"},
- :thousand => "t",
- :million => "m" ,
- :billion =>"b" ,
- :trillion =>"t" ,
- :quadrillion =>"q"
- }
- }
- },
- :percentage => { :format => {:delimiter => '', :precision => 2, :strip_insignificant_zeros => true} },
- :precision => { :format => {:delimiter => '', :significant => true} }
- },
- :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
- end
-
- def test_number_to_i18n_currency
- assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts'))
- assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts'))
- assert_equal("-10.00 - &$", number_to_currency(-10, :locale => 'ts', :format => "%n - %u"))
- end
-
- def test_number_to_currency_with_clean_i18n_settings
- clean_i18n do
- assert_equal("$10.00", number_to_currency(10))
- assert_equal("-$10.00", number_to_currency(-10))
- end
- end
-
- def test_number_to_currency_without_currency_negative_format
- clean_i18n do
- I18n.backend.store_translations 'ts', :number => { :currency => { :format => { :unit => '@', :format => '%n %u' } } }
- assert_equal("-10.00 @", number_to_currency(-10, :locale => 'ts'))
- end
- end
-
- def test_number_with_i18n_precision
- #Delimiter was set to ""
- assert_equal("10000", number_with_precision(10000, :locale => 'ts'))
-
- #Precision inherited and significant was set
- assert_equal("1.00", number_with_precision(1.0, :locale => 'ts'))
-
- end
-
- def test_number_with_i18n_delimiter
- #Delimiter "," and separator "."
- assert_equal("1,000,000.234", number_with_delimiter(1000000.234, :locale => 'ts'))
- end
-
- def test_number_to_i18n_percentage
- # to see if strip_insignificant_zeros is true
- assert_equal("1%", number_to_percentage(1, :locale => 'ts'))
- # precision is 2, significant should be inherited
- assert_equal("1.24%", number_to_percentage(1.2434, :locale => 'ts'))
- # no delimiter
- assert_equal("12434%", number_to_percentage(12434, :locale => 'ts'))
- end
-
- def test_number_to_i18n_human_size
- #b for bytes and k for kbytes
- assert_equal("2 k", number_to_human_size(2048, :locale => 'ts'))
- assert_equal("42 b", number_to_human_size(42, :locale => 'ts'))
- end
-
- def test_number_to_human_with_default_translation_scope
- #Using t for thousand
- assert_equal "2 t", number_to_human(2000, :locale => 'ts')
- #Significant was set to true with precision 2, using b for billion
- assert_equal "1.2 b", number_to_human(1234567890, :locale => 'ts')
- #Using pluralization (Ten/Tens and Tenth/Tenths)
- assert_equal "1 Tenth", number_to_human(0.1, :locale => 'ts')
- assert_equal "1.3 Tenth", number_to_human(0.134, :locale => 'ts')
- assert_equal "2 Tenths", number_to_human(0.2, :locale => 'ts')
- assert_equal "1 Ten", number_to_human(10, :locale => 'ts')
- assert_equal "1.2 Ten", number_to_human(12, :locale => 'ts')
- assert_equal "2 Tens", number_to_human(20, :locale => 'ts')
- end
-
- def test_number_to_human_with_custom_translation_scope
- #Significant was set to true with precision 2, with custom translated units
- assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human)
- end
-
- private
- def clean_i18n
- load_path = I18n.load_path.dup
- I18n.load_path.clear
- I18n.reload!
- yield
- ensure
- I18n.load_path = load_path
- I18n.reload!
- end
-end
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 057cb47f53..d8fffe75ed 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -361,69 +361,39 @@ class NumberHelperTest < ActionView::TestCase
end
def test_number_helpers_should_raise_error_if_invalid_when_specified
- assert_raise InvalidNumberError do
+ exception = assert_raise InvalidNumberError do
number_to_human("x", :raise => true)
end
- begin
- number_to_human("x", :raise => true)
- rescue InvalidNumberError => e
- assert_equal "x", e.number
- end
+ assert_equal "x", exception.number
- assert_raise InvalidNumberError do
- number_to_human_size("x", :raise => true)
- end
- begin
+ exception = assert_raise InvalidNumberError do
number_to_human_size("x", :raise => true)
- rescue InvalidNumberError => e
- assert_equal "x", e.number
end
+ assert_equal "x", exception.number
- assert_raise InvalidNumberError do
+ exception = assert_raise InvalidNumberError do
number_with_precision("x", :raise => true)
end
- begin
- number_with_precision("x", :raise => true)
- rescue InvalidNumberError => e
- assert_equal "x", e.number
- end
+ assert_equal "x", exception.number
- assert_raise InvalidNumberError do
+ exception = assert_raise InvalidNumberError do
number_to_currency("x", :raise => true)
end
- begin
- number_with_precision("x", :raise => true)
- rescue InvalidNumberError => e
- assert_equal "x", e.number
- end
+ assert_equal "x", exception.number
- assert_raise InvalidNumberError do
+ exception = assert_raise InvalidNumberError do
number_to_percentage("x", :raise => true)
end
- begin
- number_to_percentage("x", :raise => true)
- rescue InvalidNumberError => e
- assert_equal "x", e.number
- end
+ assert_equal "x", exception.number
- assert_raise InvalidNumberError do
- number_with_delimiter("x", :raise => true)
- end
- begin
+ exception = assert_raise InvalidNumberError do
number_with_delimiter("x", :raise => true)
- rescue InvalidNumberError => e
- assert_equal "x", e.number
end
+ assert_equal "x", exception.number
- assert_raise InvalidNumberError do
+ exception = assert_raise InvalidNumberError do
number_to_phone("x", :raise => true)
end
- begin
- number_to_phone("x", :raise => true)
- rescue InvalidNumberError => e
- assert_equal "x", e.number
- end
-
+ assert_equal "x", exception.number
end
-
end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index c005f040eb..387aafebd4 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -68,14 +68,14 @@ module ActionView
assert_nil self.class.determine_default_helper_class("String")
end
- test "delegates notice to request.flash" do
- view.request.flash.expects(:notice).with("this message")
- view.notice("this message")
+ test "delegates notice to request.flash[:notice]" do
+ view.request.flash.expects(:[]).with(:notice)
+ view.notice
end
- test "delegates alert to request.flash" do
- view.request.flash.expects(:alert).with("this message")
- view.alert("this message")
+ test "delegates alert to request.flash[:alert]" do
+ view.request.flash.expects(:[]).with(:alert)
+ view.alert
end
test "uses controller lookup context" do
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 2242c59131..eb06250060 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/class/attribute'
require 'active_support/deprecation'
@@ -98,7 +97,7 @@ module ActiveModel
# person.clear_name
# person.name # => nil
def attribute_method_prefix(*prefixes)
- self.attribute_method_matchers += prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }
+ self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
undefine_attribute_methods
end
@@ -133,7 +132,7 @@ module ActiveModel
# person.name # => "Bob"
# person.name_short? # => true
def attribute_method_suffix(*suffixes)
- self.attribute_method_matchers += suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }
+ self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
undefine_attribute_methods
end
@@ -169,7 +168,7 @@ module ActiveModel
# person.reset_name_to_default!
# person.name # => 'Gemma'
def attribute_method_affix(*affixes)
- self.attribute_method_matchers += affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }
+ self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
undefine_attribute_methods
end
@@ -336,15 +335,13 @@ module ActiveModel
end
def attribute_method_matcher(method_name) #:nodoc:
- if attribute_method_matchers_cache.key?(method_name)
- attribute_method_matchers_cache[method_name]
- else
+ attribute_method_matchers_cache.fetch(method_name) do |name|
# Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
# will match every time.
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
match = nil
- matchers.detect { |method| match = method.match(method_name) }
- attribute_method_matchers_cache[method_name] = match
+ matchers.detect { |method| match = method.match(name) }
+ attribute_method_matchers_cache[name] = match
end
end
@@ -352,18 +349,18 @@ module ActiveModel
# using the given `extra` args. This fallbacks `define_method`
# and `send` if the given names cannot be compiled.
def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
- if name =~ NAME_COMPILABLE_REGEXP
- defn = "def #{name}(*args)"
+ defn = if name =~ NAME_COMPILABLE_REGEXP
+ "def #{name}(*args)"
else
- defn = "define_method(:'#{name}') do |*args|"
+ "define_method(:'#{name}') do |*args|"
end
- extra = (extra.map(&:inspect) << "*args").join(", ")
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
- if send =~ CALL_COMPILABLE_REGEXP
- target = "#{"self." unless include_private}#{send}(#{extra})"
+ target = if send =~ CALL_COMPILABLE_REGEXP
+ "#{"self." unless include_private}#{send}(#{extra})"
else
- target = "send(:'#{send}', #{extra})"
+ "send(:'#{send}', #{extra})"
end
mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -379,8 +376,6 @@ module ActiveModel
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
def initialize(options = {})
- options.symbolize_keys!
-
if options[:prefix] == '' || options[:suffix] == ''
ActiveSupport::Deprecation.warn(
"Specifying an empty prefix/suffix for an attribute method is no longer " \
@@ -390,7 +385,7 @@ module ActiveModel
)
end
- @prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
+ @prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '')
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@method_name = "#{prefix}%s#{suffix}"
@@ -399,8 +394,6 @@ module ActiveModel
def match(method_name)
if @regex =~ method_name
AttributeMethodMatch.new(method_missing_target, $1, method_name)
- else
- nil
end
end
@@ -468,7 +461,7 @@ module ActiveModel
# The struct's attributes are prefix, base and suffix.
def match_attribute_method?(method_name)
match = self.class.send(:attribute_method_matcher, method_name)
- match && attribute_method?(match.attr_name) ? match : nil
+ match if match && attribute_method?(match.attr_name)
end
def missing_attribute(attr_name, stack)
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 80385c2614..e669113001 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -89,11 +89,11 @@ module ActiveModel
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
- :terminator => "result == false",
- :skip_after_callbacks_if_terminated => true,
- :scope => [:kind, :name],
- :only => [:before, :around, :after]
- }.merge(options)
+ :terminator => "result == false",
+ :skip_after_callbacks_if_terminated => true,
+ :scope => [:kind, :name],
+ :only => [:before, :around, :after]
+ }.merge!(options)
types = Array(options.delete(:only))
@@ -106,6 +106,8 @@ module ActiveModel
end
end
+ private
+
def _define_before_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.before_#{callback}(*args, &block)
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index d7f30f0920..89d87a8b6f 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -18,17 +18,23 @@ module ActiveModel
# end
#
# cm = ContactMessage.new
- # cm.to_model == self # => true
- # cm.to_key # => nil
- # cm.to_param # => nil
- # cm.to_partial_path # => "contact_messages/contact_message"
- #
+ # cm.to_model == cm # => true
+ # cm.to_key # => nil
+ # cm.to_param # => nil
+ # cm.to_partial_path # => "contact_messages/contact_message"
module Conversion
extend ActiveSupport::Concern
# If your object is already designed to implement all of the Active Model
# you can use the default <tt>:to_model</tt> implementation, which simply
- # returns self.
+ # returns +self+.
+ #
+ # class Person
+ # include ActiveModel::Conversion
+ # end
+ #
+ # person = Person.new
+ # person.to_model == person # => true
#
# If your model does not act like an Active Model object, then you should
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
@@ -37,21 +43,28 @@ module ActiveModel
self
end
- # Returns an Enumerable of all key attributes if any is set, regardless
- # if the object is persisted or not.
+ # Returns an Enumerable of all key attributes if any is set, regardless if
+ # the object is persisted or not. If there no key attributes, returns +nil+.
def to_key
key = respond_to?(:id) && id
key ? [key] : nil
end
# Returns a string representing the object's key suitable for use in URLs,
- # or nil if <tt>persisted?</tt> is false.
+ # or +nil+ if <tt>persisted?</tt> is false.
def to_param
persisted? ? to_key.join('-') : nil
end
# Returns a string identifying the path associated with the object.
# ActionPack uses this to find a suitable partial to represent the object.
+ #
+ # class Person
+ # include ActiveModel::Conversion
+ # end
+ #
+ # person = Person.new
+ # person.to_partial_path # => "people/person"
def to_partial_path
self.class._to_partial_path
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 7014d8114f..9bd5f903cd 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -80,8 +80,8 @@ module ActiveModel
# person.changes # => {"name" => ["Bill", "Bob"]}
#
# If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
- # to mark that the attribute is changing. Otherwise ActiveModel can't track changes to
- # in-place attributes.
+ # to mark that the attribute is changing. Otherwise ActiveModel can't track
+ # changes to in-place attributes.
#
# person.name_will_change!
# person.name_change # => ["Bill", "Bill"]
@@ -115,7 +115,7 @@ module ActiveModel
end
# Returns a hash of changed attributes indicating their original
- # and new values like <tt>attr => [original value, new value]</tt>.
+ # and new values like <tt>attr => [original value, new value]</tt>.
#
# person.changes # => {}
# person.name = 'bob'
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 567677bbb9..4ed3462e7e 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -54,8 +54,8 @@ module ActiveModel
# The above allows you to do:
#
# p = Person.new
- # p.validate! # => ["can not be nil"]
- # p.errors.full_messages # => ["name can not be nil"]
+ # person.validate! # => ["can not be nil"]
+ # person.errors.full_messages # => ["name can not be nil"]
# # etc..
class Errors
include Enumerable
@@ -81,28 +81,50 @@ module ActiveModel
super
end
- # Clear the messages
+ # Clear the error messages.
+ #
+ # person.errors.full_messages # => ["name can not be nil"]
+ # person.errors.clear
+ # person.errors.full_messages # => []
def clear
messages.clear
end
- # Do the error messages include an error with key +error+?
+ # Returns +true+ if the error messages include an error for the given key
+ # +attribute+, +false+ otherwise.
+ #
+ # person.errors.messages # => { :name => ["can not be nil"] }
+ # person.errors.include?(:name) # => true
+ # person.errors.include?(:age) # => false
def include?(attribute)
(v = messages[attribute]) && v.any?
end
+ # aliases include?
alias :has_key? :include?
- # Get messages for +key+
+ # Get messages for +key+.
+ #
+ # person.errors.messages # => { :name => ["can not be nil"] }
+ # person.errors.get(:name) # => ["can not be nil"]
+ # person.errors.get(:age) # => nil
def get(key)
messages[key]
end
- # Set messages for +key+ to +value+
+ # Set messages for +key+ to +value+.
+ #
+ # person.errors.get(:name) # => ["can not be nil"]
+ # person.errors.set(:name, ["can't be nil"])
+ # person.errors.get(:name) # => ["can't be nil"]
def set(key, value)
messages[key] = value
end
- # Delete messages for +key+
+ # Delete messages for +key+. Returns the deleted messages.
+ #
+ # person.errors.get(:name) # => ["can not be nil"]
+ # person.errors.delete(:name) # => ["can not be nil"]
+ # person.errors.get(:name) # => nil
def delete(key)
messages.delete(key)
end
@@ -110,16 +132,16 @@ module ActiveModel
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
- # p.errors[:name] # => ["can not be nil"]
- # p.errors['name'] # => ["can not be nil"]
+ # person.errors[:name] # => ["can not be nil"]
+ # person.errors['name'] # => ["can not be nil"]
def [](attribute)
get(attribute.to_sym) || set(attribute.to_sym, [])
end
# Adds to the supplied attribute the supplied error message.
#
- # p.errors[:name] = "must be set"
- # p.errors[:name] # => ['must be set']
+ # person.errors[:name] = "must be set"
+ # person.errors[:name] # => ['must be set']
def []=(attribute, error)
self[attribute] << error
end
@@ -128,13 +150,13 @@ module ActiveModel
# Yields the attribute and the error for that attribute. If the attribute
# has more than one error message, yields once for each error message.
#
- # p.errors.add(:name, "can't be blank")
- # p.errors.each do |attribute, error|
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
- # p.errors.add(:name, "must be specified")
- # p.errors.each do |attribute, error|
+ # person.errors.add(:name, "must be specified")
+ # person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
@@ -146,54 +168,65 @@ module ActiveModel
# Returns the number of error messages.
#
- # p.errors.add(:name, "can't be blank")
- # p.errors.size # => 1
- # p.errors.add(:name, "must be specified")
- # p.errors.size # => 2
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.size # => 1
+ # person.errors.add(:name, "must be specified")
+ # person.errors.size # => 2
def size
values.flatten.size
end
- # Returns all message values
+ # Returns all message values.
+ #
+ # person.errors.messages # => { :name => ["can not be nil", "must be specified"] }
+ # person.errors.values # => [["can not be nil", "must be specified"]]
def values
messages.values
end
- # Returns all message keys
+ # Returns all message keys.
+ #
+ # person.errors.messages # => { :name => ["can not be nil", "must be specified"] }
+ # person.errors.keys # => [:name]
def keys
messages.keys
end
- # Returns an array of error messages, with the attribute name included
+ # Returns an array of error messages, with the attribute name included.
#
- # p.errors.add(:name, "can't be blank")
- # p.errors.add(:name, "must be specified")
- # p.errors.to_a # => ["name can't be blank", "name must be specified"]
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, "must be specified")
+ # person.errors.to_a # => ["name can't be blank", "name must be specified"]
def to_a
full_messages
end
# Returns the number of error messages.
- # p.errors.add(:name, "can't be blank")
- # p.errors.count # => 1
- # p.errors.add(:name, "must be specified")
- # p.errors.count # => 2
+ #
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.count # => 1
+ # person.errors.add(:name, "must be specified")
+ # person.errors.count # => 2
def count
to_a.size
end
- # Returns true if no errors are found, false otherwise.
+ # Returns +true+ if no errors are found, +false+ otherwise.
# If the error message is a string it can be empty.
+ #
+ # person.errors.full_messages # => ["name can not be nil"]
+ # person.errors.empty? # => false
def empty?
all? { |k, v| v && v.empty? && !v.is_a?(String) }
end
+ # aliases empty?
alias_method :blank?, :empty?
# Returns an xml formatted representation of the Errors hash.
#
- # p.errors.add(:name, "can't be blank")
- # p.errors.add(:name, "must be specified")
- # p.errors.to_xml
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, "must be specified")
+ # person.errors.to_xml
# # =>
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
# # <errors>
@@ -204,14 +237,21 @@ module ActiveModel
to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
end
- # Returns an Hash that can be used as the JSON representation for this object.
- # Options:
- # * <tt>:full_messages</tt> - determines if json object should contain
- # full messages or not. Default: <tt>false</tt>.
+ # Returns a Hash that can be used as the JSON representation for this
+ # object. You can pass the <tt>:full_messages</tt> option. This determines
+ # if the json object should contain full messages or not (false by default).
+ #
+ # person.as_json # => { :name => ["can not be nil"] }
+ # person.as_json(full_messages: true) # => { :name => ["name can not be nil"] }
def as_json(options=nil)
to_hash(options && options[:full_messages])
end
+ # Returns a Hash of attributes with their error messages. If +full_messages+
+ # is +true+, it will contain full messages (see +full_message+).
+ #
+ # person.to_hash # => { :name => ["can not be nil"] }
+ # person.to_hash(true) # => { :name => ["name can not be nil"] }
def to_hash(full_messages = false)
if full_messages
messages = {}
@@ -224,12 +264,31 @@ module ActiveModel
end
end
- # Adds +message+ to the error messages on +attribute+. More than one error can be added to the same
- # +attribute+.
- # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
+ # Adds +message+ to the error messages on +attribute+. More than one error
+ # can be added to the same +attribute+. If no +message+ is supplied,
+ # <tt>:invalid</tt> is assumed.
#
- # If +message+ is a symbol, it will be translated using the appropriate scope (see +generate_message+).
- # If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
+ # person.errors.add(:name)
+ # # => ["is invalid"]
+ # person.errors.add(:name, 'must be implemented')
+ # # => ["is invalid", "must be implemented"]
+ #
+ # person.errors.messages
+ # # => { :name => ["must be implemented", "is invalid"] }
+ #
+ # If +message+ is a symbol, it will be translated using the appropriate
+ # scope (see +generate_message+).
+ #
+ # If +message+ is a proc, it will be called, allowing for things like
+ # <tt>Time.now</tt> to be used within an error.
+ #
+ # If the <tt>:strict</tt> option is set to true will raise
+ # ActiveModel::StrictValidationFailed instead of adding the error.
+ #
+ # person.errors.add(:name, nil, strict: true)
+ # # => ActiveModel::StrictValidationFailed: name is invalid
+ #
+ # person.errors.messages # => {}
def add(attribute, message = nil, options = {})
message = normalize_message(attribute, message, options)
if options[:strict]
@@ -239,7 +298,12 @@ module ActiveModel
self[attribute] << message
end
- # Will add an error message to each of the attributes in +attributes+ that is empty.
+ # Will add an error message to each of the attributes in +attributes+
+ # that is empty.
+ #
+ # person.errors.add_on_empty(:name)
+ # person.errors.messages
+ # # => { :name => ["can't be empty"] }
def add_on_empty(attributes, options = {})
[attributes].flatten.each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
@@ -248,7 +312,12 @@ module ActiveModel
end
end
- # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
+ # Will add an error message to each of the attributes in +attributes+ that
+ # is blank (using Object#blank?).
+ #
+ # person.errors.add_on_blank(:name)
+ # person.errors.messages
+ # # => { :name => ["can't be blank"] }
def add_on_blank(attributes, options = {})
[attributes].flatten.each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
@@ -256,10 +325,11 @@ module ActiveModel
end
end
- # Returns true if an error on the attribute with the given message is present, false otherwise.
- # +message+ is treated the same as for +add+.
- # p.errors.add :name, :blank
- # p.errors.added? :name, :blank # => true
+ # Returns +true+ if an error on the attribute with the given message is
+ # present, +false+ otherwise. +message+ is treated the same as for +add+.
+ #
+ # person.errors.add :name, :blank
+ # person.errors.added? :name, :blank # => true
def added?(attribute, message = nil, options = {})
message = normalize_message(attribute, message, options)
self[attribute].include? message
@@ -267,22 +337,21 @@ module ActiveModel
# Returns all the full error messages in an array.
#
- # class Company
+ # class Person
# validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
+ # validates_length_of :name, in: 5..30
# end
#
- # company = Company.create(:address => '123 First St.')
- # company.errors.full_messages # =>
- # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
+ # person = Person.create(address: '123 First St.')
+ # person.errors.full_messages
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
def full_messages
map { |attribute, message| full_message(attribute, message) }
end
# Returns a full message for a given attribute.
#
- # company.errors.full_message(:name, "is invalid") # =>
- # "Name is invalid"
+ # person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
def full_message(attribute, message)
return message if attribute == :base
attr_name = attribute.to_s.tr('.', '_').humanize
@@ -298,10 +367,11 @@ module ActiveModel
# (<tt>activemodel.errors.messages</tt>).
#
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
- # if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not
- # there also, it returns the translation of the default message
- # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
- # translated attribute name and the value are available for interpolation.
+ # if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
+ # that is not there also, it returns the translation of the default message
+ # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
+ # name, translated attribute name and the value are available for
+ # interpolation.
#
# When using inheritance in your models, it will check all the inherited
# models too, but only if the model itself hasn't been found. Say you have
@@ -317,7 +387,6 @@ module ActiveModel
# * <tt>activemodel.errors.messages.blank</tt>
# * <tt>errors.attributes.title.blank</tt>
# * <tt>errors.messages.blank</tt>
- #
def generate_message(attribute, type = :invalid, options = {})
type = options.delete(:message) if options[:message].is_a?(Symbol)
@@ -366,6 +435,8 @@ module ActiveModel
end
end
+ # Raised when a validation cannot be corrected by end users and are considered
+ # exceptional.
class StrictValidationFailed < StandardError
end
end
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 88b730626c..550fa474ea 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -12,19 +12,20 @@ module ActiveModel
# you want all features out of the box.
#
# These tests do not attempt to determine the semantic correctness of the
- # returned values. For instance, you could implement valid? to always
- # return true, and the tests would pass. It is up to you to ensure that
- # the values are semantically meaningful.
+ # returned values. For instance, you could implement <tt>valid?</tt> to
+ # always return true, and the tests would pass. It is up to you to ensure
+ # that the values are semantically meaningful.
#
- # Objects you pass in are expected to return a compliant object from a
- # call to to_model. It is perfectly fine for to_model to return self.
+ # Objects you pass in are expected to return a compliant object from a call
+ # to <tt>to_model</tt>. It is perfectly fine for <tt>to_model</tt> to return
+ # +self+.
module Tests
# == Responds to <tt>to_key</tt>
#
# Returns an Enumerable of all (primary) key attributes
- # or nil if model.persisted? is false. This is used by
- # dom_id to generate unique ids for the object.
+ # or nil if <tt>model.persisted?</tt> is false. This is used by
+ # <tt>dom_id</tt> to generate unique ids for the object.
def test_to_key
assert model.respond_to?(:to_key), "The model should respond to to_key"
def model.persisted?() false end
@@ -34,13 +35,14 @@ module ActiveModel
# == Responds to <tt>to_param</tt>
#
# Returns a string representing the object's key suitable for use in URLs
- # or nil if model.persisted? is false.
+ # or +nil+ if <tt>model.persisted?</tt> is +false+.
#
- # Implementers can decide to either raise an exception or provide a default
- # in case the record uses a composite primary key. There are no tests for this
- # behavior in lint because it doesn't make sense to force any of the possible
- # implementation strategies on the implementer. However, if the resource is
- # not persisted?, then to_param should always return nil.
+ # Implementers can decide to either raise an exception or provide a
+ # default in case the record uses a composite primary key. There are no
+ # tests for this behavior in lint because it doesn't make sense to force
+ # any of the possible implementation strategies on the implementer.
+ # However, if the resource is not persisted?, then <tt>to_param</tt>
+ # should always return +nil+.
def test_to_param
assert model.respond_to?(:to_param), "The model should respond to to_param"
def model.to_key() [1] end
@@ -50,9 +52,8 @@ module ActiveModel
# == Responds to <tt>to_partial_path</tt>
#
- # Returns a string giving a relative path. This is used for looking up
+ # Returns a string giving a relative path. This is used for looking up
# partials. For example, a BlogPost model might return "blog_posts/blog_post"
- #
def test_to_partial_path
assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
assert_kind_of String, model.to_partial_path
@@ -60,11 +61,11 @@ module ActiveModel
# == Responds to <tt>persisted?</tt>
#
- # Returns a boolean that specifies whether the object has been persisted yet.
- # This is used when calculating the URL for an object. If the object is
- # not persisted, a form for that object, for instance, will route to the
- # create action. If it is persisted, a form for the object will routes to
- # the update action.
+ # Returns a boolean that specifies whether the object has been persisted
+ # yet. This is used when calculating the URL for an object. If the object
+ # is not persisted, a form for that object, for instance, will route to
+ # the create action. If it is persisted, a form for the object will routes
+ # to the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"
@@ -73,8 +74,8 @@ module ActiveModel
# == Naming
#
# Model.model_name must return a string with some convenience methods:
- # :human, :singular, and :plural. Check ActiveModel::Naming for more information.
- #
+ # <tt>:human</tt>, <tt>:singular</tt> and <tt>:plural</tt>. 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
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index 415ab0ad17..f104d0306c 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -2,7 +2,7 @@ require 'set'
module ActiveModel
module MassAssignmentSecurity
- class PermissionSet < Set
+ class PermissionSet < Set #:nodoc:
def +(values)
super(values.compact.map(&:to_s))
@@ -23,14 +23,14 @@ module ActiveModel
end
end
- class WhiteList < PermissionSet
+ class WhiteList < PermissionSet #:nodoc:
def deny?(key)
!include?(key)
end
end
- class BlackList < PermissionSet
+ class BlackList < PermissionSet #:nodoc:
def deny?(key)
include?(key)
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index cb0a051ca5..33a530e6bd 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -2,11 +2,11 @@ module ActiveModel
# == Active Model Basic Model
#
- # Includes the required interface for an object to interact with <tt>ActionPack</tt>,
- # using different <tt>ActiveModel</tt> modules. It includes model name introspections,
- # conversions, translations and validations. Besides that, it allows you to
- # initialize the object with a hash of attributes, pretty much like
- # <tt>ActiveRecord</tt> does.
+ # Includes the required interface for an object to interact with
+ # <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules.
+ # It includes model name introspections, conversions, translations and
+ # validations. Besides that, it allows you to initialize the object with a
+ # hash of attributes, pretty much like <tt>ActiveRecord</tt> does.
#
# A minimal implementation could be:
#
@@ -15,13 +15,13 @@ module ActiveModel
# attr_accessor :name, :age
# end
#
- # person = Person.new(:name => 'bob', :age => '18')
+ # person = Person.new(name: 'bob', age: '18')
# person.name # => 'bob'
- # person.age # => 18
+ # person.age # => 18
#
- # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> to
- # return <tt>false</tt>, which is the most common case. You may want to override it
- # in your class to simulate a different scenario:
+ # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
+ # to return +false+, which is the most common case. You may want to override
+ # it in your class to simulate a different scenario:
#
# class Person
# include ActiveModel::Model
@@ -32,11 +32,12 @@ module ActiveModel
# end
# end
#
- # person = Person.new(:id => 1, :name => 'bob')
+ # person = Person.new(id: 1, name: 'bob')
# person.persisted? # => true
#
- # Also, if for some reason you need to run code on <tt>initialize</tt>, make sure you
- # call super if you want the attributes hash initialization to happen.
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make
+ # sure you call +super+ if you want the attributes hash initialization to
+ # happen.
#
# class Person
# include ActiveModel::Model
@@ -48,11 +49,12 @@ module ActiveModel
# end
# end
#
- # person = Person.new(:id => 1, :name => 'bob')
+ # person = Person.new(id: 1, name: 'bob')
# person.omg # => true
#
- # For more detailed information on other functionalities available, please refer
- # to the specific modules included in <tt>ActiveModel::Model</tt> (see below).
+ # For more detailed information on other functionalities available, please
+ # refer to the specific modules included in <tt>ActiveModel::Model</tt>
+ # (see below).
module Model
def self.included(base) #:nodoc:
base.class_eval do
@@ -63,12 +65,31 @@ module ActiveModel
end
end
+ # Initializes a new model with the given +params+.
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :name, :age
+ # end
+ #
+ # person = Person.new(name: 'bob', age: '18')
+ # person.name # => "bob"
+ # person.age # => 18
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
+ # Indicates if the model is persisted. Default is +false+.
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :id, :name
+ # end
+ #
+ # person = Person.new(id: 1, name: 'bob')
+ # person.persisted? # => false
def persisted?
false
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 2b5fc57a3a..ea4f9341c6 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -14,9 +14,137 @@ module ActiveModel
alias_method :cache_key, :collection
+ ##
+ # :method: ==
+ #
+ # :call-seq:
+ # ==(other)
+ #
+ # Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and
+ # +other+ are equal, otherwise +false+.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name == 'BlogPost' # => true
+ # BlogPost.model_name == 'Blog Post' # => false
+
+ ##
+ # :method: ===
+ #
+ # :call-seq:
+ # ===(other)
+ #
+ # Equivalent to <tt>#==</tt>.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name === 'BlogPost' # => true
+ # BlogPost.model_name === 'Blog Post' # => false
+
+ ##
+ # :method: <=>
+ #
+ # :call-seq:
+ # ==(other)
+ #
+ # Equivalent to <tt>String#<=></tt>.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name <=> 'BlogPost' # => 0
+ # BlogPost.model_name <=> 'Blog' # => 1
+ # BlogPost.model_name <=> 'BlogPosts' # => -1
+
+ ##
+ # :method: =~
+ #
+ # :call-seq:
+ # =~(regexp)
+ #
+ # Equivalent to <tt>String#=~</tt>. Match the class name against the given
+ # regexp. Returns the position where the match starts or +nil+ if there is
+ # no match.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name =~ /Post/ # => 4
+ # BlogPost.model_name =~ /\d/ # => nil
+
+ ##
+ # :method: !~
+ #
+ # :call-seq:
+ # !~(regexp)
+ #
+ # Equivalent to <tt>String#!~</tt>. Match the class name against the given
+ # regexp. Returns +true+ if there is no match, otherwise +false+.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name !~ /Post/ # => false
+ # BlogPost.model_name !~ /\d/ # => true
+
+ ##
+ # :method: eql?
+ #
+ # :call-seq:
+ # eql?(other)
+ #
+ # Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and
+ # +other+ have the same length and content, otherwise +false+.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name.eql?('BlogPost') # => true
+ # BlogPost.model_name.eql?('Blog Post') # => false
+
+ ##
+ # :method: to_s
+ #
+ # :call-seq:
+ # to_s()
+ #
+ # Returns the class name.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name.to_s # => "BlogPost"
+
+ ##
+ # :method: to_str
+ #
+ # :call-seq:
+ # to_str()
+ #
+ # Equivalent to +to_s+.
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
:to_str, :to => :name
+ # Returns a new ActiveModel::Name instance. By default, the +namespace+
+ # and +name+ option will take the namespace and name of the given class
+ # respectively.
+ #
+ # module Foo
+ # class Bar
+ # end
+ # end
+ #
+ # ActiveModel::Name.new(Foo::Bar).to_s
+ # # => "Foo::Bar"
def initialize(klass, namespace = nil, name = nil)
@name = name || klass.name
@@ -38,7 +166,11 @@ module ActiveModel
end
# Transform the model name into a more humane format, using I18n. By default,
- # it will underscore then humanize the class name
+ # it will underscore then humanize the class name.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
#
# BlogPost.model_name.human # => "Blog post"
#
@@ -82,11 +214,12 @@ module ActiveModel
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
- # is required to pass the Active Model Lint test. So either extending the provided
- # method below, or rolling your own is required.
+ # is required to pass the Active Model Lint test. So either extending the
+ # provided method below, or rolling your own is required.
module Naming
# Returns an ActiveModel::Name object for module. It can be
- # used to retrieve all kinds of naming-related information.
+ # used to retrieve all kinds of naming-related information
+ # (See ActiveModel::Name for more information).
def model_name
@_model_name ||= begin
namespace = self.parents.detect do |n|
@@ -96,7 +229,7 @@ module ActiveModel
end
end
- # Returns the plural class name of a record or class. Examples:
+ # Returns the plural class name of a record or class.
#
# ActiveModel::Naming.plural(post) # => "posts"
# ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
@@ -104,7 +237,7 @@ module ActiveModel
model_name_from_record_or_class(record_or_class).plural
end
- # Returns the singular class name of a record or class. Examples:
+ # Returns the singular class name of a record or class.
#
# ActiveModel::Naming.singular(post) # => "post"
# ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
@@ -112,10 +245,10 @@ module ActiveModel
model_name_from_record_or_class(record_or_class).singular
end
- # Identifies whether the class name of a record or class is uncountable. Examples:
+ # Identifies whether the class name of a record or class is uncountable.
#
# ActiveModel::Naming.uncountable?(Sheep) # => true
- # ActiveModel::Naming.uncountable?(Post) => false
+ # ActiveModel::Naming.uncountable?(Post) # => false
def self.uncountable?(record_or_class)
plural(record_or_class) == singular(record_or_class)
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index f5ea285ccb..ca206ee9aa 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -238,8 +238,7 @@ module ActiveModel
# Send observed_method(object) if the method exists and
# the observer is enabled for the given object's class.
def update(observed_method, object, *extra_args, &block) #:nodoc:
- return unless respond_to?(observed_method)
- return if disabled_for?(object)
+ return if !respond_to?(observed_method) || disabled_for?(object)
send(observed_method, object, *extra_args, &block)
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index cd596e37d2..55ea6be796 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -82,8 +82,7 @@ module ActiveModel
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_each(*attr_names, &block)
- options = attr_names.extract_options!.symbolize_keys
- validates_with BlockValidator, options.merge(:attributes => attr_names.flatten), &block
+ validates_with BlockValidator, _merge_attributes(attr_names), &block
end
# Adds a validation method or block to the class. This is useful when
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 38abd0c1fa..43651094cf 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -2,9 +2,9 @@ module ActiveModel
# == Active Model Acceptance Validator
module Validations
- class AcceptanceValidator < EachValidator
+ class AcceptanceValidator < EachValidator #:nodoc:
def initialize(options)
- super(options.reverse_merge(:allow_nil => true, :accept => "1"))
+ super({ :allow_nil => true, :accept => "1" }.merge!(options))
end
def validate_each(record, attribute, value)
@@ -58,7 +58,7 @@ module ActiveModel
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or
# false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index ede34d15bc..b6cf82fb19 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -2,7 +2,7 @@ module ActiveModel
# == Active Model Confirmation Validator
module Validations
- class ConfirmationValidator < EachValidator
+ class ConfirmationValidator < EachValidator #:nodoc:
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
human_attribute_name = record.class.human_attribute_name(attribute)
@@ -59,7 +59,7 @@ module ActiveModel
# <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index edd42d85f2..c8d7057606 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -4,7 +4,7 @@ module ActiveModel
# == Active Model Exclusion Validator
module Validations
- class ExclusionValidator < EachValidator
+ class ExclusionValidator < EachValidator #:nodoc:
include Clusivity
def validate_each(record, attribute, value)
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index ffdf842d94..d48987c482 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -2,7 +2,7 @@ module ActiveModel
# == Active Model Format Validator
module Validations
- class FormatValidator < EachValidator
+ class FormatValidator < EachValidator #:nodoc:
def validate_each(record, attribute, value)
if options[:with]
regexp = option_call(record, :with)
@@ -32,12 +32,12 @@ module ActiveModel
def record_error(record, attribute, name, value)
record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
end
-
+
def regexp_using_multiline_anchors?(regexp)
regexp.source.start_with?("^") ||
(regexp.source.end_with?("$") && !regexp.source.end_with?("\\$"))
end
-
+
def check_options_validity(options, name)
option = options[name]
if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
@@ -110,7 +110,7 @@ module ActiveModel
# if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
# or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
# * <tt>:multiline</tt> - Set to true if your regular expression contains
# anchors that match the beginning or end of lines as opposed to the
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 8810f2a3c1..154db5aedc 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -4,7 +4,7 @@ module ActiveModel
# == Active Model Inclusion Validator
module Validations
- class InclusionValidator < EachValidator
+ class InclusionValidator < EachValidator #:nodoc:
include Clusivity
def validate_each(record, attribute, value)
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 64b4fe2d74..40ebe0cd2e 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -2,7 +2,7 @@ module ActiveModel
# == Active Model Length Validator
module Validations
- class LengthValidator < EachValidator
+ class LengthValidator < EachValidator #:nodoc:
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 40b5b92b84..1069ed3906 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -2,7 +2,7 @@ module ActiveModel
# == Active Model Numericality Validator
module Validations
- class NumericalityValidator < EachValidator
+ class NumericalityValidator < EachValidator #:nodoc:
CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
:equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
:odd => :odd?, :even => :even?, :other_than => :!= }.freeze
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 018ef1e733..a7dcdbba3d 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -4,7 +4,7 @@ module ActiveModel
# == Active Model Presence Validator
module Validations
- class PresenceValidator < EachValidator
+ class PresenceValidator < EachValidator #:nodoc:
def validate(record)
record.errors.add_on_blank(attributes, options)
end
@@ -40,7 +40,7 @@ module ActiveModel
# if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
# or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
# proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 66cc9daa2c..3c516f8b22 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -3,12 +3,14 @@ module ActiveModel
module HelperMethods
private
def _merge_attributes(attr_names)
- options = attr_names.extract_options!
- options.merge(:attributes => attr_names.flatten)
+ options = attr_names.extract_options!.symbolize_keys
+ attr_names.flatten!
+ options[:attributes] = attr_names
+ options
end
end
- class WithValidator < EachValidator
+ class WithValidator < EachValidator #:nodoc:
def validate_each(record, attr, val)
method_name = options[:with]
@@ -61,7 +63,7 @@ module ActiveModel
# (e.g. <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
#
# If you pass any additional configuration options, they will be passed
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 00f290d964..36d94fb38b 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,57 @@
## Rails 4.0.0 (unreleased) ##
+* Add `add_reference` and `remove_reference` schema statements. Aliases, `add_belongs_to`
+ and `remove_belongs_to` are acceptable. References are reversible.
+ Examples:
+
+ # Create a user_id column
+ add_reference(:products, :user)
+ # Create a supplier_id, supplier_type columns and appropriate index
+ add_reference(:products, :supplier, polymorphic: true, index: true)
+ # Remove polymorphic reference
+ remove_reference(:products, :supplier, polymorphic: true)
+
+ *Aleksey Magusev*
+
+* Add `:default` and `:null` options to `column_exists?`.
+
+ column_exists?(:testings, :taggable_id, :integer, null: false)
+ column_exists?(:testings, :taggable_type, :string, default: 'Photo')
+
+ *Aleksey Magusev*
+
+* `ActiveRecord::Relation#inspect` now makes it clear that you are
+ dealing with a `Relation` object rather than an array:.
+
+ User.where(:age => 30).inspect
+ # => <ActiveRecord::Relation [#<User ...>, #<User ...>, ...]>
+
+ User.where(:age => 30).to_a.inspect
+ # => [#<User ...>, #<User ...>]
+
+ The number of records displayed will be limited to 10.
+
+ *Brian Cardarella, Jon Leighton & Damien Mathieu*
+
+* Add `collation` and `ctype` support to PostgreSQL. These are available for PostgreSQL 8.4 or later.
+ Example:
+
+ development:
+ adapter: postgresql
+ host: localhost
+ database: rails_development
+ username: foo
+ password: bar
+ encoding: UTF8
+ collation: ja_JP.UTF8
+ ctype: ja_JP.UTF8
+
+ *kennyj*
+
+* `FinderMethods#exists?` now returns `false` with the `false` argument.
+
+ *Egor Lynko*
+
* Added support for specifying the precision of a timestamp in the postgresql
adapter. So, instead of having to incorrectly specify the precision using the
`:limit` option, you may use `:precision`, as intended. For example, in a migration:
@@ -12,7 +64,7 @@
*Tony Schneider*
-* Allow ActiveRecord::Relation#pluck to accept multiple columns. Returns an
+* Allow `ActiveRecord::Relation#pluck` to accept multiple columns. Returns an
array of arrays containing the typecasted values:
Person.pluck(:id, :name)
@@ -53,7 +105,7 @@
*Andrew White*
-* Move HABTM validity checks to ActiveRecord::Reflection. One side effect of
+* Move HABTM validity checks to `ActiveRecord::Reflection`. One side effect of
this is to move when the exceptions are raised from the point of declaration
to when the association is built. This is consistant with other association
validity checks.
@@ -61,7 +113,7 @@
*Andrew White*
* Added `stored_attributes` hash which contains the attributes stored using
- ActiveRecord::Store. This allows you to retrieve the list of attributes
+ `ActiveRecord::Store`. This allows you to retrieve the list of attributes
you've defined.
class User < ActiveRecord::Base
@@ -817,7 +869,7 @@
* LRU cache in mysql and sqlite are now per-process caches.
- * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache keys are per process id.
+ * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache keys are per process id.
* lib/active_record/connection_adapters/sqlite_adapter.rb: ditto
*Aaron Patterson*
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 68f8bbeb1c..a62fce4756 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1167,6 +1167,8 @@ module ActiveRecord
# If true, always save the associated objects or destroy them if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated objects.
# By default, only save associated objects that are new records.
+ #
+ # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
# [:inverse_of]
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
# that is the inverse of this <tt>has_many</tt> association. Does not work in combination
@@ -1288,6 +1290,8 @@ module ActiveRecord
# If true, always save the associated object or destroy it if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated object.
# By default, only save the associated object if it's a new record.
+ #
+ # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
# [:inverse_of]
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
# that is the inverse of this <tt>has_one</tt> association. Does not work in combination
@@ -1404,6 +1408,8 @@ module ActiveRecord
# saving the parent object.
# If false, never save or destroy the associated object.
# By default, only save the associated object if it's a new record.
+ #
+ # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
# [:touch]
# If true, the associated object will be touched (the updated_at/on attributes set to now)
# when this record is either saved or destroyed. If you specify a symbol, that attribute
@@ -1589,6 +1595,8 @@ module ActiveRecord
# If false, never save or destroy the associated objects.
# By default, only save associated objects that are new records.
#
+ # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
+ #
# Option examples:
# has_and_belongs_to_many :projects
# has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index ab6d253ef8..5b41f72e52 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -158,11 +158,12 @@ module ActiveRecord
begin
send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
end
end
unless errors.empty?
- raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
+ error_descriptions = errors.map { |ex| ex.message }.join(",")
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
end
end
@@ -180,15 +181,27 @@ module ActiveRecord
end
def read_time_parameter_value(name, values_hash_from_param)
- # If Date bits were not provided, error
- raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
- # If Date bits were provided but blank, then return nil
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
+ # If column is a :time (and not :date or :timestamp) there is no need to validate if
+ # there are year/month/day fields
+ if column_for_attribute(name).type == :time
+ # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
+ {1 => 1970, 2 => 1, 3 => 1}.each do |key,value|
+ values_hash_from_param[key] ||= value
+ end
+ else
+ # else column is a timestamp, so if Date bits were not provided, error
+ if missing_parameter = [1,2,3].detect{ |position| !values_hash_from_param.has_key?(position) }
+ raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter}i)")
+ end
+
+ # If Date bits were provided but blank, then return nil
+ return nil if (1..3).any? { |position| values_hash_from_param[position].blank? }
+ end
- set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ set_values = (1..max_position).collect{ |position| values_hash_from_param[position] }
# If Time bits are not there, then default to 0
- (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
+ (3..5).each { |i| set_values[i] = set_values[i].blank? ? 0 : set_values[i] }
instantiate_time_object(name, set_values)
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 4af4d28b74..49ab3ab808 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -6,7 +6,7 @@ module ActiveRecord
included do
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
- class_attribute :serialized_attributes
+ class_attribute :serialized_attributes, instance_writer: false
self.serialized_attributes = {}
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 3b4537aab4..a6e16da730 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -56,7 +56,7 @@ module ActiveRecord
end
def select_all(arel, name = nil, binds = [])
- if @query_cache_enabled
+ if @query_cache_enabled && !locked?(arel)
sql = to_sql(arel, binds)
cache_sql(sql, binds) { super(sql, name, binds) }
else
@@ -83,6 +83,14 @@ module ActiveRecord
result.collect { |row| row.dup }
end
end
+
+ def locked?(arel)
+ if arel.respond_to?(:locked)
+ arel.locked
+ else
+ false
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 6f9f0399db..60a9eee7c7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -31,7 +31,7 @@ module ActiveRecord
# BigDecimals need to be put in a non-normalized form and quoted.
when nil then "NULL"
when BigDecimal then value.to_s('F')
- when Numeric then value.to_s
+ when Numeric, ActiveSupport::Duration then value.to_s
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value.to_s}'"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index df78ba6c5a..ef17dfbbc5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -259,7 +259,7 @@ module ActiveRecord
end # end
EOV
end
-
+
# Adds index options to the indexes hash, keyed by column name
# This is primarily used to track indexes that need to be created after the table
#
@@ -282,7 +282,7 @@ module ActiveRecord
index_options = options.delete(:index)
args.each do |col|
column("#{col}_id", :integer, options)
- column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
@@ -441,17 +441,13 @@ module ActiveRecord
# Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
#
- # t.references(:goat)
- # t.references(:goat, :polymorphic => true)
- # t.belongs_to(:goat)
+ # t.references(:user)
+ # t.belongs_to(:supplier, polymorphic: true)
+ #
def references(*args)
options = args.extract_options!
- polymorphic = options.delete(:polymorphic)
- index_options = options.delete(:index)
- args.each do |col|
- @base.add_column(@table_name, "#{col}_id", :integer, options)
- @base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
- @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ args.each do |ref_name|
+ @base.add_reference(@table_name, ref_name, options)
end
end
alias :belongs_to :references
@@ -459,18 +455,16 @@ module ActiveRecord
# Removes a reference. Optionally removes a +type+ column.
# <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
#
- # t.remove_references(:goat)
- # t.remove_references(:goat, :polymorphic => true)
- # t.remove_belongs_to(:goat)
+ # t.remove_references(:user)
+ # t.remove_belongs_to(:supplier, polymorphic: true)
+ #
def remove_references(*args)
options = args.extract_options!
- polymorphic = options.delete(:polymorphic)
- args.each do |col|
- @base.remove_column(@table_name, "#{col}_id")
- @base.remove_column(@table_name, "#{col}_type") unless polymorphic.nil?
+ args.each do |ref_name|
+ @base.remove_reference(@table_name, ref_name, options)
end
end
- alias :remove_belongs_to :remove_references
+ alias :remove_belongs_to :remove_references
# Adds a column or columns of a specified type
#
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index f5794a4e54..65c7ef0153 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -68,10 +68,12 @@ module ActiveRecord
# column_exists?(:suppliers, :name, :string, :limit => 100)
def column_exists?(table_name, column_name, type = nil, options = {})
columns(table_name).any?{ |c| c.name == column_name.to_s &&
- (!type || c.type == type) &&
- (!options[:limit] || c.limit == options[:limit]) &&
- (!options[:precision] || c.precision == options[:precision]) &&
- (!options[:scale] || c.scale == options[:scale]) }
+ (!type || c.type == type) &&
+ (!options.key?(:limit) || c.limit == options[:limit]) &&
+ (!options.key?(:precision) || c.precision == options[:precision]) &&
+ (!options.key?(:scale) || c.scale == options[:scale]) &&
+ (!options.key?(:default) || c.default == options[:default]) &&
+ (!options.key?(:null) || c.null == options[:null]) }
end
# Creates a new table with the name +table_name+. +table_name+ may either
@@ -439,6 +441,42 @@ module ActiveRecord
indexes(table_name).detect { |i| i.name == index_name }
end
+ # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
+ # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
+ #
+ # ====== Create a user_id column
+ # add_reference(:products, :user)
+ #
+ # ====== Create a supplier_id and supplier_type columns
+ # add_belongs_to(:products, :supplier, polymorphic: true)
+ #
+ # ====== Create a supplier_id, supplier_type columns and appropriate index
+ # add_reference(:products, :supplier, polymorphic: true, index: true)
+ #
+ def add_reference(table_name, ref_name, options = {})
+ polymorphic = options.delete(:polymorphic)
+ index_options = options.delete(:index)
+ add_column(table_name, "#{ref_name}_id", :integer, options)
+ add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
+ add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ end
+ alias :add_belongs_to :add_reference
+
+ # Removes the reference(s). Also removes a +type+ column if one exists.
+ # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
+ #
+ # ====== Remove the reference
+ # remove_reference(:products, :user, index: true)
+ #
+ # ====== Remove polymorphic reference
+ # remove_reference(:products, :supplier, polymorphic: true)
+ #
+ def remove_reference(table_name, ref_name, options = {})
+ remove_column(table_name, "#{ref_name}_id")
+ remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
+ end
+ alias :remove_belongs_to :remove_reference
+
# Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
# entire structure of the database.
def structure_dump
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 921278d145..df4a9d5afc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -72,6 +72,8 @@ module ActiveRecord
when /^mediumint/i; 3
when /^smallint/i; 2
when /^tinyint/i; 1
+ when /^enum\((.+)\)/i
+ $1.split(',').map{|enum| enum.strip.length - 2}.max
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 8491d42b86..dd40351a38 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -1,3 +1,5 @@
+require 'uri'
+
module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index a9940209fa..7b263fd62d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -916,7 +916,8 @@ module ActiveRecord
end
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
- # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
+ # <tt>:encoding</tt>, <tt>:collation</tt>, <tt>:ctype</tt>,
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
#
# Example:
@@ -933,6 +934,10 @@ module ActiveRecord
" TEMPLATE = \"#{value}\""
when :encoding
" ENCODING = '#{value}'"
+ when :collation
+ " LC_COLLATE = '#{value}'"
+ when :ctype
+ " LC_CTYPE = '#{value}'"
when :tablespace
" TABLESPACE = \"#{value}\""
when :connection_limit
@@ -1059,6 +1064,20 @@ module ActiveRecord
end_sql
end
+ # Returns the current database collation.
+ def collation
+ query(<<-end_sql, 'SCHEMA')[0][0]
+ SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
+ end_sql
+ end
+
+ # Returns the current database ctype.
+ def ctype
+ query(<<-end_sql, 'SCHEMA')[0][0]
+ SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
+ end_sql
+ end
+
# Returns an array of schema names.
def schema_names
query(<<-SQL, 'SCHEMA').flatten
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index a0c7e559ce..57aa47ab61 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -191,7 +191,7 @@ module ActiveRecord
:decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "datetime" },
- :time => { :name => "datetime" },
+ :time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "blob" },
:boolean => { :name => "boolean" }
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 858b667e22..5f157fde6d 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -106,13 +106,11 @@ module ActiveRecord
attr_reader :record, :attempted_action
def initialize(record, attempted_action)
+ super("Attempted to #{attempted_action} a stale object: #{record.class.name}")
@record = record
@attempted_action = attempted_action
end
- def message
- "Attempted to #{attempted_action} a stale object: #{record.class.name}"
- end
end
# Raised when association is being configured improperly or
@@ -168,9 +166,9 @@ module ActiveRecord
class AttributeAssignmentError < ActiveRecordError
attr_reader :exception, :attribute
def initialize(message, exception, attribute)
+ super(message)
@exception = exception
@attribute = attribute
- @message = message
end
end
@@ -189,12 +187,10 @@ module ActiveRecord
attr_reader :model
def initialize(model)
+ super("Unknown primary key for table #{model.table_name} in model #{model}.")
@model = model
end
- def message
- "Unknown primary key for table #{model.table_name} in model #{model}."
- end
end
class ImmutableRelation < ActiveRecordError
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 96b62fdd61..95f4360578 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -51,13 +51,15 @@ module ActiveRecord
super || delegate.respond_to?(*args)
end
- [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
+ [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default, :add_reference, :remove_reference].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args) # def create_table(*args)
record(:"#{method}", args) # record(:create_table, args)
end # end
EOV
end
+ alias :add_belongs_to :add_reference
+ alias :remove_belongs_to :remove_reference
private
@@ -102,6 +104,16 @@ module ActiveRecord
[:remove_timestamps, args]
end
+ def invert_add_reference(args)
+ [:remove_reference, args]
+ end
+ alias :invert_add_belongs_to :invert_add_reference
+
+ def invert_remove_reference(args)
+ [:add_reference, args]
+ end
+ alias :invert_remove_belongs_to :invert_remove_reference
+
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
@delegate.send(method, *args, &block)
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
index 7b3d926d91..0015e3a567 100644
--- a/activerecord/lib/active_record/model.rb
+++ b/activerecord/lib/active_record/model.rb
@@ -103,7 +103,9 @@ module ActiveRecord
def abstract_class?
false
end
-
+
+ # Defines the name of the table column which will store the class name on single-table
+ # inheritance situations.
def inheritance_column
'type'
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 841681e542..7febb5539f 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -351,7 +351,7 @@ module ActiveRecord
if respond_to?(method)
send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
- raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
+ raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
end
end
end
@@ -373,7 +373,7 @@ module ActiveRecord
# })
#
# Will update the name of the Person with ID 1, build a new associated
- # person with the name `John', and mark the associated Person with ID 2
+ # person with the name 'John', and mark the associated Person with ID 2
# for destruction.
#
# Also accepts an Array of attribute hashes:
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 8199b5c2e0..78ecb1cdc5 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -35,7 +35,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.drop_current
end
- desc "Migrate the database (options: VERSION=x, VERBOSE=false)."
+ desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task :migrate => [:environment, :load_config] do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
@@ -148,13 +148,10 @@ db_namespace = namespace :db do
# desc "Retrieves the collation for the current environment's database"
task :collation => [:environment, :load_config] do
- config = ActiveRecord::Base.configurations[Rails.env || 'development']
- case config['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.collation
- else
- $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ begin
+ puts ActiveRecord::Tasks::DatabaseTasks.collation_current
+ rescue NoMethodError
+ $stderr.puts 'Sorry, your database adapter is not supported yet, feel free to submit a patch'
end
end
@@ -270,6 +267,15 @@ db_namespace = namespace :db do
end
namespace :structure do
+ def set_firebird_env(config)
+ ENV['ISC_USER'] = config['username'].to_s if config['username']
+ ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
+ end
+
+ def firebird_db_string(config)
+ FireRuby::Database.db_string_for(config.symbolize_keys)
+ end
+
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
task :dump => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
@@ -304,7 +310,7 @@ db_namespace = namespace :db do
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[env]['adapter']
when /mysql/, /postgresql/, /sqlite/
- ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[Rails.env], filename)
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[env], filename)
when 'sqlserver'
`sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}`
when 'oci', 'oracle'
@@ -338,6 +344,13 @@ db_namespace = namespace :db do
end
end
+ # desc "Recreate the test database from an existent schema.rb file"
+ task :load_schema => 'db:test:purge' do
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
+ ActiveRecord::Schema.verbose = false
+ db_namespace["schema:load"].invoke
+ end
+
# desc "Recreate the test database from an existent structure.sql file"
task :load_structure => 'db:test:purge' do
begin
@@ -348,15 +361,18 @@ db_namespace = namespace :db do
end
end
- # desc "Recreate the test database from an existent schema.rb file"
- task :load_schema => 'db:test:purge' do
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
- ActiveRecord::Schema.verbose = false
- db_namespace["schema:load"].invoke
+ # desc "Recreate the test database from a fresh schema"
+ task :clone do
+ case ActiveRecord::Base.schema_format
+ when :ruby
+ db_namespace["test:clone_schema"].invoke
+ when :sql
+ db_namespace["test:clone_structure"].invoke
+ end
end
# desc "Recreate the test database from a fresh schema.rb file"
- task :clone => %w(db:schema:dump db:test:load_schema)
+ task :clone_schema => ["db:schema:dump", "db:test:load_schema"]
# desc "Recreate the test database from a fresh structure.sql file"
task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ]
@@ -389,7 +405,7 @@ db_namespace = namespace :db do
# desc 'Check for pending migrations and load the test schema'
task :prepare => 'db:abort_if_pending_migrations' do
unless ActiveRecord::Base.configurations.blank?
- db_namespace[{ :sql => 'test:clone_structure', :ruby => 'test:load' }[ActiveRecord::Base.schema_format]].invoke
+ db_namespace['test:load'].invoke
end
end
end
@@ -405,7 +421,7 @@ db_namespace = namespace :db do
# desc "Clear the sessions table"
task :clear => [:environment, :load_config] do
- ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}"
+ ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::SessionStore::Session.table_name}"
end
end
end
@@ -416,7 +432,7 @@ namespace :railties do
task :migrations => :'db:load_config' do
to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip }
railties = {}
- Rails.application.railties.all do |railtie|
+ Rails.application.railties.each do |railtie|
next unless to_load == :all || to_load.include?(railtie.railtie_name)
if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first)
@@ -440,15 +456,3 @@ end
task 'test:prepare' => 'db:test:prepare'
-def session_table_name
- ActiveRecord::SessionStore::Session.table_name
-end
-
-def set_firebird_env(config)
- ENV['ISC_USER'] = config['username'].to_s if config['username']
- ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
-end
-
-def firebird_db_string(config)
- FireRuby::Database.db_string_for(config.symbolize_keys)
-end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index fe3aa00a74..a39328b89b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -492,10 +492,6 @@ module ActiveRecord
end
end
- def inspect
- to_a.inspect
- end
-
def pretty_print(q)
q.pp(self.to_a)
end
@@ -518,6 +514,16 @@ module ActiveRecord
@values.dup
end
+ def inspect
+ limit = [limit_value, 11].compact.min
+ entries = loaded? ? to_a.take(limit) : limit(limit)
+
+ entries.map!(&:inspect)
+ entries[10] = '...' if entries.size == 11
+
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
+ end
+
private
def references_eager_loaded_tables?
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 86eb8f35b5..e40b958b54 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -262,10 +262,16 @@ module ActiveRecord
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
- group_attr = group_values
- association = @klass.reflect_on_association(group_attr.first.to_sym)
- associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
- group_fields = Array(associated ? association.foreign_key : group_attr)
+ group_attrs = group_values
+
+ if group_attrs.first.respond_to?(:to_sym)
+ association = @klass.reflect_on_association(group_attrs.first.to_sym)
+ associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
+ group_fields = Array(associated ? association.foreign_key : group_attrs)
+ else
+ group_fields = group_attrs
+ end
+
group_aliases = group_fields.map { |field| column_alias_for(field) }
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
[aliaz, column_for(field)]
@@ -288,10 +294,14 @@ module ActiveRecord
select_values += select_values unless having_values.empty?
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
- "#{field} AS #{aliaz}"
+ if field.respond_to?(:as)
+ field.as(aliaz)
+ else
+ "#{field} AS #{aliaz}"
+ end
}
- relation = except(:group).group(group.join(','))
+ relation = except(:group).group(group)
relation.select_values = select_values
calculated_data = @klass.connection.select_all(relation, nil, bind_values)
@@ -303,10 +313,10 @@ module ActiveRecord
end
Hash[calculated_data.map do |row|
- key = group_columns.map { |aliaz, column|
+ key = group_columns.map { |aliaz, column|
type_cast_calculated_value(row[aliaz], column)
}
- key = key.first if key.size == 1
+ key = key.first if key.size == 1
key = key_records[key] if associated
[key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
end]
@@ -321,6 +331,7 @@ module ActiveRecord
# column_alias_for("count(*)") # => "count_all"
# column_alias_for("count", "id") # => "count_id"
def column_alias_for(*keys)
+ keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k}
table_name = keys.join(' ')
table_name.downcase!
table_name.gsub!(/\*/, 'all')
@@ -332,7 +343,7 @@ module ActiveRecord
end
def column_for(field)
- field_name = field.to_s.split('.').last
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
@klass.columns.detect { |c| c.name.to_s == field_name }
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index c91758265b..974cd326ef 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -146,8 +146,8 @@ module ActiveRecord
to_a
end
- # Returns true if a record exists in the table that matches the +id+ or
- # conditions given, or false otherwise. The argument can take five forms:
+ # Returns +true+ if a record exists in the table that matches the +id+ or
+ # conditions given, or +false+ otherwise. The argument can take six forms:
#
# * Integer - Finds the record with this primary key.
# * String - Finds the record with a primary key corresponding to this
@@ -155,8 +155,9 @@ module ActiveRecord
# * Array - Finds the record that matches these +find+-style conditions
# (such as <tt>['color = ?', 'red']</tt>).
# * Hash - Finds the record that matches these +find+-style conditions
- # (such as <tt>{:color => 'red'}</tt>).
- # * No args - Returns false if the table is empty, true otherwise.
+ # (such as <tt>{color: 'red'}</tt>).
+ # * +false+ - Returns always +false+.
+ # * No args - Returns +false+ if the table is empty, +true+ otherwise.
#
# For more information about specifying conditions as a Hash or Array,
# see the Conditions section in the introduction to ActiveRecord::Base.
@@ -168,21 +169,22 @@ module ActiveRecord
# Person.exists?(5)
# Person.exists?('5')
# Person.exists?(['name LIKE ?', "%#{query}%"])
- # Person.exists?(:name => "David")
+ # Person.exists?(name: 'David')
+ # Person.exists?(false)
# Person.exists?
- def exists?(id = false)
- id = id.id if ActiveRecord::Model === id
- return false if id.nil?
+ def exists?(conditions = :none)
+ conditions = conditions.id if ActiveRecord::Model === conditions
+ return false if !conditions
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
relation = relation.except(:select, :order).select("1 AS one").limit(1)
- case id
+ case conditions
when Array, Hash
- relation = relation.where(id)
+ relation = relation.where(conditions)
else
- relation = relation.where(table[primary_key].eq(id)) if id
+ relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
end
connection.select_value(relation, "#{name} Exists", relation.bind_values)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 5e5aca0396..6f49548aab 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -83,7 +83,9 @@ module ActiveRecord
end
def references!(*args)
- self.references_values = (references_values + args.flatten.map(&:to_s)).uniq
+ args.flatten!
+
+ self.references_values = (references_values + args.map!(&:to_s)).uniq
self
end
@@ -134,7 +136,9 @@ module ActiveRecord
end
def group!(*args)
- self.group_values += args.flatten
+ args.flatten!
+
+ self.group_values += args
self
end
@@ -143,11 +147,10 @@ module ActiveRecord
end
def order!(*args)
- args = args.flatten
+ args.flatten!
references = args.reject { |arg| Arel::Node === arg }
- .map { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }
- .compact
+ references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
references!(references) if references.any?
self.order_values += args
@@ -168,8 +171,10 @@ module ActiveRecord
end
def reorder!(*args)
+ args.flatten!
+
self.reordering_value = true
- self.order_values = args.flatten
+ self.order_values = args
self
end
@@ -327,7 +332,7 @@ module ActiveRecord
#
# Should be used with order.
#
- # User.offset(10).order("name ASC")
+ # User.offset(10).order("name ASC")
def offset(value)
spawn.offset!(value)
end
@@ -410,10 +415,10 @@ module ActiveRecord
#
# Can accept other relation objects. For example:
#
- # Topic.select('title').from(Topics.approved)
+ # Topic.select('title').from(Topic.approved)
# # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
#
- # Topics.select('a.title').from(Topics.approved, :a)
+ # Topic.select('a.title').from(Topic.approved, :a)
# # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
#
def from(value, subquery_name = nil)
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index d13491502e..d836acf18f 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -43,7 +43,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :stored_attributes
+ class_attribute :stored_attributes, instance_writer: false
self.stored_attributes = {}
end
@@ -58,8 +58,11 @@ module ActiveRecord
keys.each do |key|
define_method("#{key}=") do |value|
initialize_store_attribute(store_attribute)
- send(store_attribute)[key] = value
- send :"#{store_attribute}_will_change!"
+ attribute = send(store_attribute)
+ if value != attribute[key]
+ attribute[key] = value
+ send :"#{store_attribute}_will_change!"
+ end
end
define_method(key) do
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index ff4f41dbeb..fb3dfc2730 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -60,6 +60,15 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).charset
end
+ def collation_current(environment = Rails.env)
+ collation ActiveRecord::Base.configurations[environment]
+ end
+
+ def collation(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).collation
+ end
+
def purge(configuration)
class_for_adapter(configuration['adapter']).new(configuration).purge
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index b39cd2282f..bf62dfd5b5 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -44,6 +44,10 @@ module ActiveRecord
connection.charset
end
+ def collation
+ connection.collation
+ end
+
def structure_dump(filename)
establish_connection configuration
File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index a210392e53..ea5cb888fb 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -29,6 +29,10 @@ module ActiveRecord
connection.encoding
end
+ def collation
+ connection.collation
+ end
+
def purge
clear_active_connections!
drop
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb
index d56f9f57a4..2cca17b94f 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb
@@ -1,7 +1,7 @@
<% module_namespacing do -%>
class <%= class_name %> < <%= parent_class_name.classify %>
<% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
- belongs_to :<%= attribute.name %>
+ belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %>
<% end -%>
<% if !accessible_attributes.empty? -%>
attr_accessible <%= accessible_attributes.map {|a| ":#{a.name}" }.sort.join(', ') %>
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/module.rb b/activerecord/lib/rails/generators/active_record/model/templates/module.rb
index fca2908080..a3bf1c37b6 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/module.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/module.rb
@@ -1,7 +1,7 @@
<% module_namespacing do -%>
module <%= class_path.map(&:camelize).join('::') %>
def self.table_name_prefix
- '<%= class_path.join('_') %>_'
+ '<%= namespaced? ? namespaced_class_path.join('_') : class_path.join('_') %>_'
end
end
<% end -%>
diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb
new file mode 100644
index 0000000000..40af317ad1
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/enum_test.rb
@@ -0,0 +1,10 @@
+require "cases/helper"
+
+class MysqlEnumTest < ActiveRecord::TestCase
+ class EnumTest < ActiveRecord::Base
+ end
+
+ def test_enum_limit
+ assert_equal 5, EnumTest.columns.first.limit
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
new file mode 100644
index 0000000000..f3a05e48ad
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -0,0 +1,10 @@
+require "cases/helper"
+
+class Mysql2EnumTest < ActiveRecord::TestCase
+ class EnumTest < ActiveRecord::Base
+ end
+
+ def test_enum_limit
+ assert_equal 5, EnumTest.columns.first.limit
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 447d729e52..113c27b194 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -21,6 +21,10 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1)
end
+ def test_create_database_with_collation_and_ctype
+ assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'UTF8' LC_COLLATE = 'ja_JP.UTF8' LC_CTYPE = 'ja_JP.UTF8'), create_database(:aimonetti, :encoding => :"UTF8", :collation => :"ja_JP.UTF8", :ctype => :"ja_JP.UTF8")
+ end
+
def test_add_index
# add_index calls index_name_exists? which can't work since execute is stubbed
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*|
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index f6e168caf2..f823ce33d8 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -21,6 +21,14 @@ module ActiveRecord
assert_not_nil @connection.encoding
end
+ def test_collation
+ assert_not_nil @connection.collation
+ end
+
+ def test_ctype
+ assert_not_nil @connection.ctype
+ end
+
def test_default_client_min_messages
assert_equal "warning", @connection.client_min_messages
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 5e947799cc..4e26c5dda1 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -24,7 +24,7 @@ module ActiveRecord
@conn.extend(LogIntercepter)
@conn.intercepted = true
end
-
+
def teardown
@conn.intercepted = false
@conn.logged = []
@@ -43,11 +43,6 @@ module ActiveRecord
assert(!result.rows.first.include?("blob"), "should not store blobs")
end
- def test_time_column
- owner = Owner.create!(:eats_at => Time.utc(1995,1,1,6,0))
- assert_match(/1995-01-01/, owner.reload.eats_at.to_s)
- end
-
def test_exec_insert
column = @conn.columns('items').find { |col| col.name == 'number' }
vals = [[column, 10]]
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index b8481175b2..3ea6201d60 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -193,7 +193,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_no_sql_should_be_fired_if_association_already_loaded
Car.create(:name => 'honda')
bulbs = Car.first.bulbs
- bulbs.inspect # to load all instances of bulbs
+ bulbs.to_a # to load all instances of bulbs
assert_no_queries do
bulbs.first()
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 1093fedea1..fe385feb4a 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -792,6 +792,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
private
+
def cached_columns
Topic.columns.find_all { |column|
!Topic.serialized_attributes.include? column.name
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 2fee553cef..e34f505a02 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -81,6 +81,12 @@ end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
+ def setup
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
+ end
+
def test_generated_methods_modules
modules = Computer.ancestors
assert modules.include?(Computer::GeneratedFeatureMethods)
@@ -504,7 +510,7 @@ class BasicsTest < ActiveRecord::TestCase
end
# Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter)
def test_utc_as_time_zone
Topic.default_timezone = :utc
attributes = { "bonus_time" => "5:42:00AM" }
@@ -686,7 +692,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
def test_multiparameter_attributes_on_time_with_no_date
@@ -746,9 +752,6 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
- ActiveRecord::Base.time_zone_aware_attributes = false
- ActiveRecord::Base.default_timezone = :local
- Time.zone = nil
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
"written_on(5i)" => "12", "written_on(6i)" => "02"
@@ -796,8 +799,6 @@ class BasicsTest < ActiveRecord::TestCase
topic = Topic.find(1)
topic.attributes = attributes
assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
- ensure
- ActiveRecord::Base.default_timezone = :local
end
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
@@ -813,14 +814,9 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on
assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
assert_equal Time.zone, topic.written_on.time_zone
- ensure
- ActiveRecord::Base.time_zone_aware_attributes = false
- ActiveRecord::Base.default_timezone = :local
- Time.zone = nil
end
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
- ActiveRecord::Base.time_zone_aware_attributes = false
Time.zone = ActiveSupport::TimeZone[-28800]
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
@@ -830,8 +826,6 @@ class BasicsTest < ActiveRecord::TestCase
topic.attributes = attributes
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
assert_equal false, topic.written_on.respond_to?(:time_zone)
- ensure
- Time.zone = nil
end
def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
@@ -848,14 +842,11 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
assert_equal false, topic.written_on.respond_to?(:time_zone)
ensure
- ActiveRecord::Base.time_zone_aware_attributes = false
- ActiveRecord::Base.default_timezone = :local
- Time.zone = nil
Topic.skip_time_zone_conversion_for_attributes = []
end
# Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter)
def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
ActiveRecord::Base.time_zone_aware_attributes = true
ActiveRecord::Base.default_timezone = :utc
@@ -868,17 +859,10 @@ class BasicsTest < ActiveRecord::TestCase
topic.attributes = attributes
assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
assert topic.bonus_time.utc?
- ensure
- ActiveRecord::Base.time_zone_aware_attributes = false
- ActiveRecord::Base.default_timezone = :local
- Time.zone = nil
end
end
def test_multiparameter_attributes_on_time_with_empty_seconds
- ActiveRecord::Base.time_zone_aware_attributes = false
- ActiveRecord::Base.default_timezone = :local
- Time.zone = nil
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
@@ -888,36 +872,51 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
- def test_multiparameter_assignment_of_aggregation_with_missing_values
- ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(2)" => address.city, "address(3)" => address.country }
- customer.attributes = attributes
- end
- assert_equal("address", ex.errors[0].attribute)
+ def test_multiparameter_attributes_setting_time_attribute
+ return skip "Oracle does not have TIME data type" if current_adapter? :OracleAdapter
+
+ topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" )
+ assert_equal 1, topic.bonus_time.hour
+ assert_equal 5, topic.bonus_time.min
end
- def test_multiparameter_assignment_of_aggregation_with_large_index
- ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country }
- customer.attributes = attributes
+ def test_multiparameter_attributes_setting_date_attribute
+ topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11" )
+ assert_equal 1952, topic.written_on.year
+ assert_equal 3, topic.written_on.month
+ assert_equal 11, topic.written_on.day
+ end
+
+ def test_multiparameter_attributes_setting_date_and_time_attribute
+ topic = Topic.new(
+ "written_on(1i)" => "1952",
+ "written_on(2i)" => "3",
+ "written_on(3i)" => "11",
+ "written_on(4i)" => "13",
+ "written_on(5i)" => "55")
+ assert_equal 1952, topic.written_on.year
+ assert_equal 3, topic.written_on.month
+ assert_equal 11, topic.written_on.day
+ assert_equal 13, topic.written_on.hour
+ assert_equal 55, topic.written_on.min
+ end
+
+ def test_multiparameter_attributes_setting_time_but_not_date_on_date_field
+ assert_raise( ActiveRecord::MultiparameterAssignmentErrors ) do
+ Topic.new( "written_on(4i)" => "13", "written_on(5i)" => "55" )
end
- assert_equal("address", ex.errors[0].attribute)
end
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
- return true if current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
+ return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
attributes = {
"bonus_time" => "5:42:00AM"
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
end
def test_boolean
@@ -1882,8 +1881,6 @@ class BasicsTest < ActiveRecord::TestCase
est_key = Developer.first.cache_key
assert_equal utc_key, est_key
- ensure
- ActiveRecord::Base.time_zone_aware_attributes = false
end
def test_cache_key_format_for_existing_record_with_updated_at
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 4df613488a..e1c1e449ef 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -58,7 +58,16 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_group_by_field
c = Account.group(:firm_id).sum(:credit_limit)
- [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
+ [1,6,2].each do |firm_id|
+ assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}"
+ end
+ end
+
+ def test_should_group_by_arel_attribute
+ c = Account.group(Account.arel_table[:firm_id]).sum(:credit_limit)
+ [1,6,2].each do |firm_id|
+ assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}"
+ end
end
def test_should_group_by_multiple_fields
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index 4111a5f808..a7b63d15c9 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -76,6 +76,12 @@ module ActiveRecord
date_string = Time.now.utc.strftime("%F")
assert_equal date_string, column.type_cast(date_string).strftime("%F")
end
+
+ def test_type_cast_duration_to_integer
+ column = Column.new("field", nil, "integer")
+ assert_equal 1800, column.type_cast(30.minutes)
+ assert_equal 7200, column.type_cast(2.hours)
+ end
end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index e62d984ebd..576a455f09 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -55,6 +55,10 @@ class FinderTest < ActiveRecord::TestCase
assert Topic.exists?
end
+ def test_exists_returns_false_with_false_arg
+ assert !Topic.exists?(false)
+ end
+
# exists? should handle nil for id's that come from URLs and always return false
# (example: Topic.exists?(params[:id])) where params[:id] is nil
def test_exists_with_nil_arg
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index afff020561..44d08a8ee4 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -5,7 +5,6 @@ require 'config'
gem 'minitest'
require 'minitest/autorun'
require 'stringio'
-require 'mocha'
require 'cases/test_case'
require 'active_record'
diff --git a/activerecord/test/cases/inclusion_test.rb b/activerecord/test/cases/inclusion_test.rb
index 8726ba5e51..9b9c09d2d8 100644
--- a/activerecord/test/cases/inclusion_test.rb
+++ b/activerecord/test/cases/inclusion_test.rb
@@ -4,7 +4,7 @@ require 'models/teapot'
class BasicInclusionModelTest < ActiveRecord::TestCase
def test_basic_model
Teapot.create!(:name => "Ronnie Kemper")
- assert_equal "Ronnie Kemper", Teapot.find(1).name
+ assert_equal "Ronnie Kemper", Teapot.first.name
end
def test_initialization
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index ab61a4dcef..ce9be66069 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -291,14 +291,20 @@ module ActiveRecord
def test_column_exists_with_definition
connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- t.column :bar, :decimal, :precision => 8, :scale => 2
+ t.column :foo, :string, limit: 100
+ t.column :bar, :decimal, precision: 8, scale: 2
+ t.column :taggable_id, :integer, null: false
+ t.column :taggable_type, :string, default: 'Photo'
end
- assert connection.column_exists?(:testings, :foo, :string, :limit => 100)
- refute connection.column_exists?(:testings, :foo, :string, :limit => 50)
- assert connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2)
- refute connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2)
+ assert connection.column_exists?(:testings, :foo, :string, limit: 100)
+ refute connection.column_exists?(:testings, :foo, :string, limit: nil)
+ assert connection.column_exists?(:testings, :bar, :decimal, precision: 8, scale: 2)
+ refute connection.column_exists?(:testings, :bar, :decimal, precision: nil, scale: nil)
+ assert connection.column_exists?(:testings, :taggable_id, :integer, null: false)
+ refute connection.column_exists?(:testings, :taggable_id, :integer, null: true)
+ assert connection.column_exists?(:testings, :taggable_type, :string, default: 'Photo')
+ refute connection.column_exists?(:testings, :taggable_type, :string, default: nil)
end
def test_column_exists_on_table_with_no_options_parameter_supplied
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 063209389f..4614be9650 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -30,61 +30,57 @@ module ActiveRecord
def test_references_column_type_adds_id
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ @connection.expect :add_reference, nil, [:delete_me, :customer, {}]
t.references :customer
end
end
def test_remove_references_column_type_removes_id
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ @connection.expect :remove_reference, nil, [:delete_me, :customer, {}]
t.remove_references :customer
end
end
def test_add_belongs_to_works_like_add_references
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ @connection.expect :add_reference, nil, [:delete_me, :customer, {}]
t.belongs_to :customer
end
end
def test_remove_belongs_to_works_like_remove_references
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ @connection.expect :remove_reference, nil, [:delete_me, :customer, {}]
t.remove_belongs_to :customer
end
end
def test_references_column_type_with_polymorphic_adds_type
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {}]
- @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {}]
- t.references :taggable, :polymorphic => true
+ @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true]
+ t.references :taggable, polymorphic: true
end
end
def test_remove_references_column_type_with_polymorphic_removes_type
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
- @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
- t.remove_references :taggable, :polymorphic => true
+ @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true]
+ t.remove_references :taggable, polymorphic: true
end
end
def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {:null => false}]
- @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {:null => false}]
- t.references :taggable, :polymorphic => true, :null => false
+ @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false]
+ t.references :taggable, polymorphic: true, null: false
end
end
def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
- @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
- t.remove_references :taggable, :polymorphic => true, :null => false
+ @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false]
+ t.remove_references :taggable, polymorphic: true, null: false
end
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 7d026961be..f2213ee6aa 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -110,9 +110,9 @@ module ActiveRecord
end
def test_invert_add_index_with_name
- @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}]
- remove = @recorder.inverse.first
- assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove
+ @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove
end
def test_invert_add_index_with_no_options
@@ -138,6 +138,30 @@ module ActiveRecord
add = @recorder.inverse.first
assert_equal [:add_timestamps, [:table]], add
end
+
+ def test_invert_add_reference
+ @recorder.record :add_reference, [:table, :taggable, { polymorphic: true }]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }]], remove
+ end
+
+ def test_invert_add_belongs_to_alias
+ @recorder.record :add_belongs_to, [:table, :user]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_reference, [:table, :user]], remove
+ end
+
+ def test_invert_remove_reference
+ @recorder.record :remove_reference, [:table, :taggable, { polymorphic: true }]
+ add = @recorder.inverse.first
+ assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }]], add
+ end
+
+ def test_invert_remove_belongs_to_alias
+ @recorder.record :remove_belongs_to, [:table, :user]
+ add = @recorder.inverse.first
+ assert_equal [:add_reference, [:table, :user]], add
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 0428d9ba76..8b91b3bc92 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -10,60 +10,53 @@ module ActiveRecord
@connection = ActiveRecord::Base.connection
end
+ def teardown
+ super
+ %w(artists_musics musics_videos catalog).each do |table_name|
+ connection.drop_table table_name if connection.tables.include?(table_name)
+ end
+ end
+
def test_create_join_table
connection.create_join_table :artists, :musics
assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
- ensure
- connection.drop_table :artists_musics
end
def test_create_join_table_set_not_null_by_default
connection.create_join_table :artists, :musics
assert_equal [false, false], connection.columns(:artists_musics).map(&:null)
- ensure
- connection.drop_table :artists_musics
end
def test_create_join_table_with_strings
connection.create_join_table 'artists', 'musics'
assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
- ensure
- connection.drop_table :artists_musics
end
def test_create_join_table_with_the_proper_order
connection.create_join_table :videos, :musics
assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort
- ensure
- connection.drop_table :musics_videos
end
def test_create_join_table_with_the_table_name
connection.create_join_table :artists, :musics, :table_name => :catalog
assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort
- ensure
- connection.drop_table :catalog
end
def test_create_join_table_with_the_table_name_as_string
connection.create_join_table :artists, :musics, :table_name => 'catalog'
assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort
- ensure
- connection.drop_table :catalog
end
def test_create_join_table_with_column_options
connection.create_join_table :artists, :musics, :column_options => {:null => true}
assert_equal [true, true], connection.columns(:artists_musics).map(&:null)
- ensure
- connection.drop_table :artists_musics
end
end
end
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
index fe53510ba2..768ebc5861 100644
--- a/activerecord/test/cases/migration/helper.rb
+++ b/activerecord/test/cases/migration/helper.rb
@@ -14,8 +14,10 @@ module ActiveRecord
module TestHelper
attr_reader :connection, :table_name
+ CONNECTION_METHODS = %w[add_column remove_column rename_column add_index change_column rename_table column_exists? index_exists? add_reference add_belongs_to remove_reference remove_references remove_belongs_to]
+
class TestModel < ActiveRecord::Base
- self.table_name = 'test_models'
+ self.table_name = :test_models
end
def setup
@@ -36,29 +38,8 @@ module ActiveRecord
end
private
- def add_column(*args)
- connection.add_column(*args)
- end
-
- def remove_column(*args)
- connection.remove_column(*args)
- end
-
- def rename_column(*args)
- connection.rename_column(*args)
- end
- def add_index(*args)
- connection.add_index(*args)
- end
-
- def change_column(*args)
- connection.change_column(*args)
- end
-
- def rename_table(*args)
- connection.rename_table(*args)
- end
+ delegate(*CONNECTION_METHODS, to: :connection)
end
end
end
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
new file mode 100644
index 0000000000..144302bd4a
--- /dev/null
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -0,0 +1,111 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class ReferencesStatementsTest < ActiveRecord::TestCase
+ include ActiveRecord::Migration::TestHelper
+
+ self.use_transactional_fixtures = false
+
+ def setup
+ super
+ @table_name = :test_models
+
+ add_column table_name, :supplier_id, :integer
+ add_index table_name, :supplier_id
+ end
+
+ def test_creates_reference_id_column
+ add_reference table_name, :user
+ assert column_exists?(table_name, :user_id, :integer)
+ end
+
+ def test_does_not_create_reference_type_column
+ add_reference table_name, :taggable
+ refute column_exists?(table_name, :taggable_type, :string)
+ end
+
+ def test_creates_reference_type_column
+ add_reference table_name, :taggable, polymorphic: true
+ assert column_exists?(table_name, :taggable_type, :string)
+ end
+
+ def test_creates_reference_id_index
+ add_reference table_name, :user, index: true
+ assert index_exists?(table_name, :user_id)
+ end
+
+ def test_does_not_create_reference_id_index
+ add_reference table_name, :user
+ refute index_exists?(table_name, :user_id)
+ end
+
+ def test_creates_polymorphic_index
+ add_reference table_name, :taggable, polymorphic: true, index: true
+ assert index_exists?(table_name, [:taggable_id, :taggable_type])
+ end
+
+ def test_creates_reference_type_column_with_default
+ add_reference table_name, :taggable, polymorphic: { default: 'Photo' }, index: true
+ assert column_exists?(table_name, :taggable_type, :string, default: 'Photo')
+ end
+
+ def test_creates_named_index
+ add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id' }
+ assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id')
+ end
+
+ def test_deletes_reference_id_column
+ remove_reference table_name, :supplier
+ refute column_exists?(table_name, :supplier_id, :integer)
+ end
+
+ def test_deletes_reference_id_index
+ remove_reference table_name, :supplier
+ refute index_exists?(table_name, :supplier_id)
+ end
+
+ def test_does_not_delete_reference_type_column
+ with_polymorphic_column do
+ remove_reference table_name, :supplier
+
+ refute column_exists?(table_name, :supplier_id, :integer)
+ assert column_exists?(table_name, :supplier_type, :string)
+ end
+ end
+
+ def test_deletes_reference_type_column
+ with_polymorphic_column do
+ remove_reference table_name, :supplier, polymorphic: true
+ refute column_exists?(table_name, :supplier_type, :string)
+ end
+ end
+
+ def test_deletes_polymorphic_index
+ with_polymorphic_column do
+ remove_reference table_name, :supplier, polymorphic: true
+ refute index_exists?(table_name, [:supplier_id, :supplier_type])
+ end
+ end
+
+ def test_add_belongs_to_alias
+ add_belongs_to table_name, :user
+ assert column_exists?(table_name, :user_id, :integer)
+ end
+
+ def test_remove_belongs_to_alias
+ remove_belongs_to table_name, :supplier
+ refute column_exists?(table_name, :supplier_id, :integer)
+ end
+
+ private
+
+ def with_polymorphic_column
+ add_column table_name, :supplier_type, :string
+ add_index table_name, [:supplier_id, :supplier_type]
+
+ yield
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 3daa033ed0..3a234f0cc1 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -196,7 +196,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to
- assert_raise_with_message ArgumentError, "Cannot build association looter. Are you trying to build a polymorphic one-to-one association?" do
+ assert_raise_with_message ArgumentError, "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?" do
Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"})
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 08f655d7fa..0153e74604 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -164,6 +164,14 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
end
+
+ def test_cache_is_ignored_for_locked_relations
+ task = Task.find 1
+
+ Task.cache do
+ assert_queries(2) { task.lock!; task.lock! }
+ end
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 80ee74e41e..3dd11ae89d 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -216,6 +216,14 @@ module ActiveRecord
def test_string_with_crazy_column
assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo))
end
+
+ def test_quote_duration
+ assert_equal "1800", @quoter.quote(30.minutes)
+ end
+
+ def test_quote_duration_int_column
+ assert_equal "7200", @quoter.quote(2.hours, FakeColumn.new(:integer))
+ end
end
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 8544d36aa8..8713b8d5e4 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1311,4 +1311,25 @@ class RelationTest < ActiveRecord::TestCase
relation.merge! where: 'foo'
end
end
+
+ test "relations show the records in #inspect" do
+ relation = Post.limit(2)
+ assert_equal "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>", relation.inspect
+ end
+
+ test "relations limit the records in #inspect at 10" do
+ relation = Post.limit(11)
+ assert_equal "#<ActiveRecord::Relation [#{Post.limit(10).map(&:inspect).join(', ')}, ...]>", relation.inspect
+ end
+
+ test "already-loaded relations don't perform a new query in #inspect" do
+ relation = Post.limit(2)
+ relation.to_a
+
+ expected = "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>"
+
+ assert_no_queries do
+ assert_equal expected, relation.inspect
+ end
+ end
end
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index a4c065e667..ce167509c1 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -51,4 +51,11 @@ class SerializationTest < ActiveRecord::TestCase
assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}"
end
end
+
+ def test_serialized_attributes_are_class_level_settings
+ assert_raise NoMethodError do
+ topic = Topic.new
+ topic.serialized_attributes = []
+ end
+ end
end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index e401dd4b12..3e60b62fd5 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -34,6 +34,11 @@ class StoreTest < ActiveRecord::TestCase
assert @john.settings_changed?
end
+ test "updating the store won't mark it as changed if an attribute isn't changed" do
+ @john.color = @john.color
+ assert !@john.settings_changed?
+ end
+
test "object initialization with not nullable column" do
assert_equal true, @john.remember_login
end
@@ -115,4 +120,11 @@ class StoreTest < ActiveRecord::TestCase
test "stored attributes are returned" do
assert_equal [:color, :homepage], Admin::User.stored_attributes[:settings]
end
+
+ test "stores_attributes are class level settings" do
+ assert_raise NoMethodError do
+ @john.stored_attributes = {}
+ end
+ end
+
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index f5c6b58b2f..4f3489b7a5 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -267,6 +267,17 @@ module ActiveRecord
end
end
+ class DatabaseTasksCollationTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_collation") do
+ eval("@#{v}").expects(:collation)
+ ActiveRecord::Tasks::DatabaseTasks.collation 'adapter' => k
+ end
+ end
+ end
+
class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 42a11b0171..9a0eb423bd 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -195,6 +195,24 @@ module ActiveRecord
end
end
+ class MysqlDBCollationTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_collation
+ @connection.expects(:collation)
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
class MySQLStructureDumpTest < ActiveRecord::TestCase
def setup
@connection = stub(:structure_dump => true)
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index e8769bd4df..62acd53003 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -38,6 +38,14 @@ module ActiveRecord
merge('encoding' => 'latin')
end
+ def test_creates_database_with_given_collation_and_ctype
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'utf8', 'collation' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8'))
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.
+ merge('collation' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8')
+ end
+
def test_establishes_connection_to_new_database
ActiveRecord::Base.expects(:establish_connection).with(@configuration)
@@ -151,6 +159,24 @@ module ActiveRecord
end
end
+ class PostgreSQLDBCollationTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_collation
+ @connection.expects(:collation)
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
class PostgreSQLStructureDumpTest < ActiveRecord::TestCase
def setup
@connection = stub(:structure_dump => true)
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index b5557fc953..06a1d0ffc2 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -124,6 +124,27 @@ module ActiveRecord
end
end
+ class SqliteDBCollationTest < ActiveRecord::TestCase
+ def setup
+ @database = 'db_create.sqlite3'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ File.stubs(:exist?).returns(false)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration, '/rails/root'
+ end
+ end
+ end
+
class SqliteStructureDumpTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 9fefa9ad3a..43cde4ab73 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -64,7 +64,6 @@ class AuditLog < ActiveRecord::Base
belongs_to :unvalidated_developer, :class_name => 'Developer'
end
-DeveloperSalary = Struct.new(:amount)
class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base
self.table_name = 'developers'
has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id'
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 65b6f9f227..24a43d7ece 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -32,4 +32,13 @@ CREATE TABLE collation_tests (
) CHARACTER SET utf8 COLLATE utf8_general_ci
SQL
+ ActiveRecord::Base.connection.execute <<-SQL
+DROP TABLE IF EXISTS enum_tests;
+SQL
+
+ ActiveRecord::Base.connection.execute <<-SQL
+CREATE TABLE enum_tests (
+ enum_column ENUM('true','false')
+)
+SQL
end
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index 7d324f98c4..802c08b819 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -43,4 +43,14 @@ CREATE TABLE collation_tests (
) CHARACTER SET utf8 COLLATE utf8_general_ci
SQL
+ ActiveRecord::Base.connection.execute <<-SQL
+DROP TABLE IF EXISTS enum_tests;
+SQL
+
+ ActiveRecord::Base.connection.execute <<-SQL
+CREATE TABLE enum_tests (
+ enum_column ENUM('true','false')
+)
+SQL
+
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index ba01ef35f0..00688eab37 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -437,7 +437,6 @@ ActiveRecord::Schema.define do
t.string :name
t.column :updated_at, :datetime
t.column :happy_at, :datetime
- t.column :eats_at, :time
t.string :essay_id
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 00fcfe2001..df144dd00b 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,6 +1,10 @@
## Rails 4.0.0 (unreleased) ##
-* Add `Time#prev_quarter' and 'Time#next_quarter' short-hands for months_ago(3) and months_since(3). *SungHee Kang*
+* `Time#change` now works with time values with offsets other than UTC or the local time zone. *Andrew White*
+
+* `ActiveSupport::Callbacks`: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev*
+
+* Add `Time#prev_quarter` and `Time#next_quarter` short-hands for `months_ago(3)` and `months_since(3)`. *SungHee Kang*
* Remove obsolete and unused `require_association` method from dependencies. *fxn*
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index fa38d5c1e3..836bc2f9cf 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -22,4 +22,5 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.6')
s.add_dependency('multi_json', '~> 1.3')
s.add_dependency('tzinfo', '~> 0.3.33')
+ s.add_dependency('minitest', '~> 3.2')
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 0aa3efbb63..6cc875c69a 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -283,7 +283,8 @@ module ActiveSupport
filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{kind}(context, &block) filter(context, &block) end
RUBY_EVAL
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
+ elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around)
+ ActiveSupport::Deprecation.warn("Filter object with #before and #after methods is deprecated. Define #around method instead.")
def filter.around(context)
should_continue = before(context)
yield if should_continue
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 4fb8c7af3f..307ae40398 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -98,15 +98,15 @@ module ActiveSupport
names.each do |name|
raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/
- reader, line = "def #{name}; config.#{name}; end", __LINE__
- writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__
+ reader, reader_line = "def #{name}; config.#{name}; end", __LINE__
+ writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__
- singleton_class.class_eval reader, __FILE__, line
- singleton_class.class_eval writer, __FILE__, line
+ singleton_class.class_eval reader, __FILE__, reader_line
+ singleton_class.class_eval writer, __FILE__, writer_line
unless options[:instance_accessor] == false
- class_eval reader, __FILE__, line unless options[:instance_reader] == false
- class_eval writer, __FILE__, line unless options[:instance_writer] == false
+ class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false
+ class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 1e0de651c7..d6ae031c0d 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -1,6 +1,5 @@
require 'active_support/xml_mini'
require 'active_support/core_ext/hash/keys'
-require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/string/inflections'
class Array
@@ -62,14 +61,10 @@ class Array
:last_word_connector => ', and '
}
if defined?(I18n)
- namespace = 'support.array.'
- default_connectors.each_key do |name|
- i18n_key = (namespace + name.to_s).to_sym
- default_connectors[name] = I18n.translate i18n_key, :locale => options[:locale]
- end
+ i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
+ default_connectors.merge!(i18n_connectors)
end
-
- options.reverse_merge! default_connectors
+ options = default_connectors.merge!(options)
case length
when 0
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index 13d659f52a..7c3a5eaace 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -4,10 +4,6 @@ require 'active_support/core_ext/date_time/calculations'
require 'active_support/values/time_zone'
class DateTime
- # Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows
- # DateTimes outside the range of what can be created with Time.
- remove_method :to_time
-
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
@@ -57,16 +53,6 @@ class DateTime
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
- # Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class.
- # If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time.
- def to_time
- if offset == 0
- ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000)
- else
- self
- end
- end
-
# Returns DateTime with local offset for given year if format is local else offset is zero
#
# DateTime.civil_from_format :local, 2012
@@ -94,8 +80,11 @@ class DateTime
private
+ def offset_in_seconds
+ (offset * 86400).to_i
+ end
+
def seconds_since_unix_epoch
- seconds_per_day = 86_400
- (self - ::DateTime.civil(1970)) * seconds_per_day
+ (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 5a61906222..c82da3c6c2 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -4,13 +4,6 @@ class Hash
#
# @person.update_attributes(params[:person].except(:admin))
#
- # If the receiver responds to +convert_key+, the method is called on each of the
- # arguments. This allows +except+ to play nice with hashes with indifferent access
- # for instance:
- #
- # {:a => 1}.with_indifferent_access.except(:a) # => {}
- # {:a => 1}.with_indifferent_access.except('a') # => {}
- #
def except(*keys)
dup.except!(*keys)
end
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 8e728691c6..e753e36124 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -7,7 +7,7 @@ class Hash
# # => { "NAME" => "Rob", "AGE" => "28" }
def transform_keys
result = {}
- keys.each do |key|
+ each_key do |key|
result[yield(key)] = self[key]
end
result
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 28c8b53b78..0a71fc117c 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,5 +1,6 @@
require 'active_support/duration'
require 'active_support/core_ext/time/conversions'
+require 'active_support/time_with_zone'
class Time
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
@@ -85,16 +86,21 @@ class Time
# (hour, min, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
# minute is passed, then sec and usec is set to 0.
def change(options)
- ::Time.send(
- utc? ? :utc_time : :local_time,
- options.fetch(:year, year),
- options.fetch(:month, month),
- options.fetch(:day, day),
- options.fetch(:hour, hour),
- options.fetch(:min, options[:hour] ? 0 : min),
- options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec),
- options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
- )
+ new_year = options.fetch(:year, year)
+ new_month = options.fetch(:month, month)
+ new_day = options.fetch(:day, day)
+ new_hour = options.fetch(:hour, hour)
+ new_min = options.fetch(:min, options[:hour] ? 0 : min)
+ new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+
+ if utc?
+ ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ elsif zone
+ ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ else
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
+ end
end
# Uses Date to provide precise Time calculations for years, months, and days.
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 48c39d9370..1cc852a3e6 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -101,7 +101,19 @@ module ActiveSupport
end
def updated_at(paths)
- @updated_at || paths.map { |path| File.mtime(path) }.max || Time.at(0)
+ @updated_at || max_mtime(paths) || Time.at(0)
+ end
+
+ # This method returns the maximum mtime of the files in +paths+, or +nil+
+ # if the array is empty.
+ #
+ # Files with a mtime in the future are ignored. Such abnormal situation
+ # can happen for example if the user changes the clock by hand. It is
+ # healthy to consider this edge case because with mtimes in the future
+ # reloading is not triggered.
+ def max_mtime(paths)
+ time_now = Time.now
+ paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max
end
def compile_glob(hash)
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index c04c2ed15b..ca2d8cb270 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -1,3 +1,5 @@
+require 'active_support/inflector/inflections'
+
module ActiveSupport
Inflector.inflections do |inflect|
inflect.plural(/$/, 's')
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 600e353812..c9e50a9462 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -2,6 +2,8 @@ require 'active_support/core_ext/array/prepend_and_append'
module ActiveSupport
module Inflector
+ extend self
+
# A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
# inflection rules.
#
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 2acc6ddee5..c14a43de0d 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,6 +1,7 @@
# encoding: utf-8
require 'active_support/inflector/inflections'
+require 'active_support/inflections'
module ActiveSupport
# The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index c736041066..99f6489adb 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -7,9 +7,14 @@ module ActiveSupport
module NumberHelper
extend self
+ DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
+
DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
:precision => 2, :significant => false, :strip_insignificant_zeros => false }
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+
# Formats a +number+ into a US phone number (e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
@@ -116,8 +121,7 @@ module ActiveSupport
number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
end
- formatted_number = format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
- formatted_number
+ format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -160,9 +164,7 @@ module ActiveSupport
options = defaults.merge!(options)
format = options[:format] || "%n%"
-
- formatted_number = format.gsub('%n', self.number_to_rounded(number, options))
- formatted_number
+ format.gsub('%n', self.number_to_rounded(number, options))
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -242,10 +244,9 @@ module ActiveSupport
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
# # => 1.111,23
def number_to_rounded(number, options = {})
- options = options.symbolize_keys
-
return number unless valid_float?(number)
- number = Float(number)
+ number = Float(number)
+ options = options.symbolize_keys
defaults = format_translations('precision', options[:locale])
options = defaults.merge!(options)
@@ -254,7 +255,7 @@ module ActiveSupport
significant = options.delete :significant
strip_insignificant_zeros = options.delete :strip_insignificant_zeros
- if significant and precision > 0
+ if significant && precision > 0
if number == 0
digits, rounded_number = 1, 0
else
@@ -263,10 +264,10 @@ module ActiveSupport
digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
end
precision -= digits
- precision = precision > 0 ? precision : 0 #don't let it be negative
+ precision = 0 if precision < 0 # don't let it be negative
else
rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
- rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
+ rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
end
formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
if strip_insignificant_zeros
@@ -277,8 +278,6 @@ module ActiveSupport
end
end
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
-
# Formats the bytes in +number+ into a more understandable
# representation (e.g., giving it 1500 yields 1.5 KB). This
# method is useful for reporting file sizes to users. You can
@@ -356,9 +355,6 @@ module ActiveSupport
end
end
- DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
- -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
-
# Pretty prints (formats and approximates) a number in a way it
# is more readable by humans (eg.: 1200000000 becomes "1.2
# Billion"). This is useful for numbers that can get very large
@@ -527,6 +523,5 @@ module ActiveSupport
false
end
private_module_and_instance_method :valid_float?
-
end
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index e2b46a235a..a6f3b43792 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,15 +1,18 @@
+gem 'minitest' # make sure we get the gem, not stdlib
require 'minitest/spec'
require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
require 'active_support/testing/deprecation'
require 'active_support/testing/declarative'
require 'active_support/testing/isolation'
-require 'active_support/testing/mochaing'
+require 'active_support/testing/mocha_module'
require 'active_support/core_ext/kernel/reporting'
module ActiveSupport
class TestCase < ::MiniTest::Spec
+ include ActiveSupport::Testing::MochaModule
+
if MiniTest::Unit::VERSION < '2.6.1'
class << self
alias :name :to_s
diff --git a/activesupport/lib/active_support/testing/mocha_module.rb b/activesupport/lib/active_support/testing/mocha_module.rb
new file mode 100644
index 0000000000..ed2942d23a
--- /dev/null
+++ b/activesupport/lib/active_support/testing/mocha_module.rb
@@ -0,0 +1,22 @@
+module ActiveSupport
+ module Testing
+ module MochaModule
+ begin
+ require 'mocha_standalone'
+ include Mocha::API
+
+ def before_setup
+ mocha_setup
+ super
+ end
+
+ def after_teardown
+ super
+ mocha_verify
+ mocha_teardown
+ end
+ rescue LoadError
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/mochaing.rb b/activesupport/lib/active_support/testing/mochaing.rb
deleted file mode 100644
index 4ad75a6681..0000000000
--- a/activesupport/lib/active_support/testing/mochaing.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-begin
- silence_warnings { require 'mocha' }
-rescue LoadError
- # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh.
- Object.const_set :Mocha, Module.new
- Mocha.const_set :ExpectationError, Class.new(StandardError)
-end \ No newline at end of file
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 527fa555b7..a65148cf1f 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -4,20 +4,11 @@ require 'active_support/callbacks'
module ActiveSupport
module Testing
module SetupAndTeardown
-
- PASSTHROUGH_EXCEPTIONS = [
- NoMemoryError,
- SignalException,
- Interrupt,
- SystemExit
- ]
-
extend ActiveSupport::Concern
included do
include ActiveSupport::Callbacks
define_callbacks :setup, :teardown
-
end
module ClassMethods
@@ -30,28 +21,15 @@ module ActiveSupport
end
end
- def run(runner)
- result = '.'
- begin
- run_callbacks :setup do
- result = super
- end
- rescue *PASSTHROUGH_EXCEPTIONS
- raise
- rescue Exception => e
- result = runner.puke(self.class, method_name, e)
- ensure
- begin
- run_callbacks :teardown
- rescue *PASSTHROUGH_EXCEPTIONS
- raise
- rescue Exception => e
- result = runner.puke(self.class, method_name, e)
- end
- end
- result
+ def before_setup
+ super
+ run_callbacks :setup
end
+ def after_teardown
+ run_callbacks :teardown
+ super
+ end
end
end
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 28bc06f103..cc3e6a4bf3 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -254,8 +254,7 @@ module ActiveSupport
# Time.utc(2000).to_f # => 946684800.0
# Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00
def at(secs)
- utc = Time.at(secs).utc rescue DateTime.civil(1970).since(secs)
- utc.in_time_zone(self)
+ Time.at(secs).utc.in_time_zone(self)
end
# Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string.
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 57ed4a6b60..25ed962c23 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -18,8 +18,6 @@ end
require 'minitest/autorun'
require 'empty_bool'
-silence_warnings { require 'mocha' }
-
ENV['NO_RELOAD'] = '1'
require 'active_support'
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 58835c0ac5..9dfa2cbf11 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -90,6 +90,12 @@ class ArrayExtToSentenceTests < ActiveSupport::TestCase
def test_one_non_string_element
assert_equal '1', [1].to_sentence
end
+
+ def test_does_not_modify_given_hash
+ options = { words_connector: ' ' }
+ assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options)
+ assert_equal({ words_connector: ' ' }, options)
+ end
end
class ArrayExtToSTests < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 183d58482d..21b7efdc73 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -427,6 +427,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_to_i
assert_equal 946684800, DateTime.civil(2000).to_i
+ assert_equal 946684800, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_i
end
protected
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index d6f285598e..412aef9301 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -459,6 +459,15 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(:min => 45)
end
+ def test_offset_change
+ assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2006)
+ assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:month => 6)
+ assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2012, :month => 9)
+ assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16)
+ assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16, :min => 45)
+ assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:min => 45)
+ end
+
def test_advance
assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 1)
assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:months => 4)
@@ -503,6 +512,28 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)
end
+ def test_offset_advance
+ assert_equal Time.new(2006,2,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 1)
+ assert_equal Time.new(2005,6,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:months => 4)
+ assert_equal Time.new(2005,3,21,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3)
+ assert_equal Time.new(2005,3,25,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.5)
+ assert_in_delta Time.new(2005,3,26,12,51,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.7), 1
+ assert_equal Time.new(2005,3,5,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5)
+ assert_equal Time.new(2005,3,6,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.5)
+ assert_in_delta Time.new(2005,3,6,8,3,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.7), 1
+ assert_equal Time.new(2012,9,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 7)
+ assert_equal Time.new(2013,10,3,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 19, :days => 11)
+ assert_equal Time.new(2013,10,17,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5)
+ assert_equal Time.new(2001,12,27,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => -3, :months => -2, :days => -1)
+ assert_equal Time.new(2005,2,28,15,15,10,'-08:00'), Time.new(2004,2,29,15,15,10,'-08:00').advance(:years => 1) #leap day plus one year
+ assert_equal Time.new(2005,2,28,20,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5)
+ assert_equal Time.new(2005,2,28,15,22,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:minutes => 7)
+ assert_equal Time.new(2005,2,28,15,15,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:seconds => 9)
+ assert_equal Time.new(2005,2,28,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5, :minutes => 7, :seconds => 9)
+ assert_equal Time.new(2005,2,28,10,8,1,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => -5, :minutes => -7, :seconds => -9)
+ assert_equal Time.new(2013,10,17,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)
+ end
+
def test_advance_with_nsec
t = Time.at(0, Rational(108635108, 1000))
assert_equal t, t.advance(:months => 0)
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
index 8adff5de8d..bd1df0f858 100644
--- a/activesupport/test/file_update_checker_test.rb
+++ b/activesupport/test/file_update_checker_test.rb
@@ -48,6 +48,21 @@ class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
assert_equal 1, i
end
+ def test_should_be_robust_to_handle_files_with_wrong_modified_time
+ i = 0
+ now = Time.now
+ time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future
+ File.utime time, time, FILES[2]
+
+ checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
+
+ sleep(1)
+ FileUtils.touch(FILES[0..1])
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
def test_should_cache_updated_result_until_execute
i = 0
checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb
index 4f2027f4eb..ddbba444cf 100644
--- a/activesupport/test/i18n_test.rb
+++ b/activesupport/test/i18n_test.rb
@@ -97,4 +97,9 @@ class I18nTest < ActiveSupport::TestCase
I18n.backend.store_translations 'en', :support => { :array => { :two_words_connector => default_two_words_connector } }
I18n.backend.store_translations 'en', :support => { :array => { :last_word_connector => default_last_word_connector } }
end
+
+ def test_to_sentence_with_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+ assert_equal 'a, b, and c', %w[a b c].to_sentence(locale: 'empty')
+ end
end
diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb
new file mode 100644
index 0000000000..e07198027b
--- /dev/null
+++ b/activesupport/test/number_helper_i18n_test.rb
@@ -0,0 +1,114 @@
+require 'abstract_unit'
+require 'active_support/number_helper'
+
+module ActiveSupport
+ class NumberHelperI18nTest < ActiveSupport::TestCase
+ include ActiveSupport::NumberHelper
+
+ def setup
+ I18n.backend.store_translations 'ts',
+ :number => {
+ :format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false },
+ :currency => { :format => { :unit => '&$', :format => '%u - %n', :negative_format => '(%u - %n)', :precision => 2 } },
+ :human => {
+ :format => {
+ :precision => 2,
+ :significant => true,
+ :strip_insignificant_zeros => true
+ },
+ :storage_units => {
+ :format => "%n %u",
+ :units => {
+ :byte => "b",
+ :kb => "k"
+ }
+ },
+ :decimal_units => {
+ :format => "%n %u",
+ :units => {
+ :deci => {:one => "Tenth", :other => "Tenths"},
+ :unit => "u",
+ :ten => {:one => "Ten", :other => "Tens"},
+ :thousand => "t",
+ :million => "m",
+ :billion =>"b",
+ :trillion =>"t" ,
+ :quadrillion =>"q"
+ }
+ }
+ },
+ :percentage => { :format => {:delimiter => '', :precision => 2, :strip_insignificant_zeros => true} },
+ :precision => { :format => {:delimiter => '', :significant => true} }
+ },
+ :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ end
+
+ def test_number_to_i18n_currency
+ assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts'))
+ assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts'))
+ assert_equal("-10.00 - &$", number_to_currency(-10, :locale => 'ts', :format => "%n - %u"))
+ end
+
+ def test_number_to_currency_with_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("$10.00", number_to_currency(10, :locale => 'empty'))
+ assert_equal("-$10.00", number_to_currency(-10, :locale => 'empty'))
+ end
+
+ def test_number_to_currency_without_currency_negative_format
+ I18n.backend.store_translations 'no_negative_format', :number => {
+ :currency => { :format => { :unit => '@', :format => '%n %u' } }
+ }
+
+ assert_equal("-10.00 @", number_to_currency(-10, :locale => 'no_negative_format'))
+ end
+
+ def test_number_with_i18n_precision
+ #Delimiter was set to ""
+ assert_equal("10000", number_to_rounded(10000, :locale => 'ts'))
+
+ #Precision inherited and significant was set
+ assert_equal("1.00", number_to_rounded(1.0, :locale => 'ts'))
+ end
+
+ def test_number_with_i18n_delimiter
+ #Delimiter "," and separator "."
+ assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'ts'))
+ end
+
+ def test_number_to_i18n_percentage
+ # to see if strip_insignificant_zeros is true
+ assert_equal("1%", number_to_percentage(1, :locale => 'ts'))
+ # precision is 2, significant should be inherited
+ assert_equal("1.24%", number_to_percentage(1.2434, :locale => 'ts'))
+ # no delimiter
+ assert_equal("12434%", number_to_percentage(12434, :locale => 'ts'))
+ end
+
+ def test_number_to_i18n_human_size
+ #b for bytes and k for kbytes
+ assert_equal("2 k", number_to_human_size(2048, :locale => 'ts'))
+ assert_equal("42 b", number_to_human_size(42, :locale => 'ts'))
+ end
+
+ def test_number_to_human_with_default_translation_scope
+ #Using t for thousand
+ assert_equal "2 t", number_to_human(2000, :locale => 'ts')
+ #Significant was set to true with precision 2, using b for billion
+ assert_equal "1.2 b", number_to_human(1234567890, :locale => 'ts')
+ #Using pluralization (Ten/Tens and Tenth/Tenths)
+ assert_equal "1 Tenth", number_to_human(0.1, :locale => 'ts')
+ assert_equal "1.3 Tenth", number_to_human(0.134, :locale => 'ts')
+ assert_equal "2 Tenths", number_to_human(0.2, :locale => 'ts')
+ assert_equal "1 Ten", number_to_human(10, :locale => 'ts')
+ assert_equal "1.2 Ten", number_to_human(12, :locale => 'ts')
+ assert_equal "2 Tens", number_to_human(20, :locale => 'ts')
+ end
+
+ def test_number_to_human_with_custom_translation_scope
+ #Significant was set to true with precision 2, with custom translated units
+ assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human)
+ end
+ end
+end
diff --git a/guides/code/getting_started/test/test_helper.rb b/guides/code/getting_started/test/test_helper.rb
index 8bf1192ffe..3daca18a71 100644
--- a/guides/code/getting_started/test/test_helper.rb
+++ b/guides/code/getting_started/test/test_helper.rb
@@ -3,7 +3,7 @@ require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
- # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
diff --git a/guides/source/4_0_release_notes.textile b/guides/source/4_0_release_notes.textile
index 23c5220c24..b7ac11999a 100644
--- a/guides/source/4_0_release_notes.textile
+++ b/guides/source/4_0_release_notes.textile
@@ -64,6 +64,16 @@ h3. Documentation
h3. Railties
+* Allow scaffold/model/migration generators to accept a <tt>polymorphic</tt> modifier for <tt>references</tt>/<tt>belongs_to</tt>, for instance
+
+<shell>
+rails g model Product supplier:references{polymorphic}
+</shell>
+
+will generate the model with <tt>belongs_to :supplier, polymorphic: true</tt> association and appropriate migration.
+
+* Set <tt>config.active_record.migration_error</tt> to <tt>:page_load</tt> for development.
+
* Add runner to <tt>Rails::Railtie</tt> as a hook called just after runner starts.
* Add <tt>/rails/info/routes</tt> path which displays the same information as +rake routes+.
@@ -102,12 +112,32 @@ h4(#railties_deprecations). Deprecations
h3. Action Mailer
-* No changes.
+* Allow to set default Action Mailer options via <tt>config.action_mailer.default_options=</tt>.
+
+* Raise an <tt>ActionView::MissingTemplate</tt> exception when no implicit template could be found.
+
+* Asynchronously send messages via the Rails Queue.
h3. Action Pack
h4. Action Controller
+* Remove Active Model dependency from Action Pack.
+
+* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping:
+
+<ruby>
+get Rack::Utils.escape('こんにちは') => 'home#index'
+</ruby>
+
+You just have to write the unicode route:
+
+<ruby>
+get 'こんにちは' => 'home#index'
+</ruby>
+
+* Return proper format on exceptions.
+
* Extracted redirect logic from <tt>ActionController::ForceSSL::ClassMethods.force_ssl</tt> into <tt>ActionController::ForceSSL#force_ssl_redirect</tt>.
* URL path parameters with invalid encoding now raise <tt>ActionController::BadRequest</tt>.
@@ -156,6 +186,8 @@ h5(#actioncontroller_deprecations). Deprecations
h4. Action Dispatch
+* Include <tt>mounted_helpers</tt> (helpers for accessing mounted engines) in <tt>ActionDispatch::IntegrationTest</tt> by default.
+
* Added <tt>ActionDispatch::SSL</tt> middleware that when included force all the requests to be under HTTPS protocol.
* Copy literal route constraints to defaults so that url generation know about them. The copied constraints are <tt>:protocol</tt>, <tt>:subdomain</tt>, <tt>:domain</tt>, <tt>:host</tt> and <tt>:port</tt>.
@@ -179,12 +211,14 @@ If <tt>:patch</tt> is the default verb for updates, edits are tunneled as <tt>PA
* Turn off verbose mode of <tt>rack-cache</tt>, we still have <tt>X-Rack-Cache</tt> to check that info.
-* Include mounted_helpers (helpers for accessing mounted engines) in <tt>ActionDispatch::IntegrationTest</tt> by default.
-
h5(#actiondispatch_deprecations). Deprecations
h4. Action View
+* Remove Active Model dependency from Action Pack.
+
+* Allow to use <tt>mounted_helpers</tt> (helpers for accessing mounted engines) in <tt>ActionView::TestCase</tt>.
+
* Make current object and counter (when it applies) variables accessible when rendering templates with <tt>:object</tt> or <tt>:collection</tt>.
* Allow to lazy load +default_form_builder+ by passing a string instead of a constant.
@@ -205,8 +239,6 @@ h4. Action View
* Removed old +text_helper+ apis for +highlight+, +excerpt+ and +word_wrap+.
-* Allow to use mounted_helpers (helpers for accessing mounted engines) in <tt>ActionView::TestCase</tt>.
-
* Remove the leading \n added by textarea on +assert_select+.
* Changed default value for <tt>config.action_view.embed_authenticity_token_in_remote_forms</tt> to false. This change breaks remote forms that need to work also without JavaScript, so if you need such behavior, you can either set it to true or explicitly pass <tt>:authenticity_token => true</tt> in form options.
@@ -299,6 +331,146 @@ Moved into a separate gem <tt>sprockets-rails</tt>.
h3. Active Record
+* Add <tt>add_reference</tt> and <tt>remove_reference</tt> schema statements. Aliases, <tt>add_belongs_to</tt> and <tt>remove_belongs_to</tt> are acceptable. References are reversible.
+
+<ruby>
+# Create a user_id column
+add_reference(:products, :user)
+
+# Create a supplier_id, supplier_type columns and appropriate index
+add_reference(:products, :supplier, polymorphic: true, index: true)
+
+# Remove polymorphic reference
+remove_reference(:products, :supplier, polymorphic: true)
+</ruby>
+
+
+* Add <tt>:default</tt> and <tt>:null</tt> options to <tt>column_exists?</tt>.
+
+<ruby>
+column_exists?(:testings, :taggable_id, :integer, null: false)
+column_exists?(:testings, :taggable_type, :string, default: 'Photo')
+</ruby>
+
+* <tt>ActiveRecord::Relation#inspect</tt> now makes it clear that you are dealing with a <tt>Relation</tt> object rather than an array:
+
+<ruby>
+User.where(:age => 30).inspect
+# => <ActiveRecord::Relation [#<User ...>, #<User ...>]>
+
+User.where(:age => 30).to_a.inspect
+# => [#<User ...>, #<User ...>]
+</ruby>
+
+if more than 10 items are returned by the relation, inspect will only show the first 10 followed by ellipsis.
+
+* Add <tt>:collation</tt> and <tt>:ctype</tt> support to PostgreSQL. These are available for PostgreSQL 8.4 or later.
+
+<yaml>
+development:
+ adapter: postgresql
+ host: localhost
+ database: rails_development
+ username: foo
+ password: bar
+ encoding: UTF8
+ collation: ja_JP.UTF8
+ ctype: ja_JP.UTF8
+</yaml>
+
+* <tt>FinderMethods#exists?</tt> now returns <tt>false</tt> with the <tt>false</tt> argument.
+
+* Added support for specifying the precision of a timestamp in the postgresql adapter. So, instead of having to incorrectly specify the precision using the <tt>:limit</tt> option, you may use <tt>:precision</tt>, as intended. For example, in a migration:
+
+<ruby>
+def change
+ create_table :foobars do |t|
+ t.timestamps :precision => 0
+ end
+end
+</ruby>
+
+* Allow <tt>ActiveRecord::Relation#pluck</tt> to accept multiple columns. Returns an array of arrays containing the typecasted values:
+
+<ruby>
+Person.pluck(:id, :name)
+# SELECT people.id, people.name FROM people
+# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
+</ruby>
+
+* Improve the derivation of HABTM join table name to take account of nesting. It now takes the table names of the two models, sorts them lexically and then joins them, stripping any common prefix from the second table name. Some examples:
+
+<plain>
+Top level models (Category <=> Product)
+Old: categories_products
+New: categories_products
+
+Top level models with a global table_name_prefix (Category <=> Product)
+Old: site_categories_products
+New: site_categories_products
+
+Nested models in a module without a table_name_prefix method (Admin::Category <=> Admin::Product)
+Old: categories_products
+New: categories_products
+
+Nested models in a module with a table_name_prefix method (Admin::Category <=> Admin::Product)
+Old: categories_products
+New: admin_categories_products
+
+Nested models in a parent model (Catalog::Category <=> Catalog::Product)
+Old: categories_products
+New: catalog_categories_products
+
+Nested models in different parent models (Catalog::Category <=> Content::Page)
+Old: categories_pages
+New: catalog_categories_content_pages
+</plain>
+
+* Move HABTM validity checks to <tt>ActiveRecord::Reflection</tt>. One side effect of this is to move when the exceptions are raised from the point of declaration to when the association is built. This is consistant with other association validity checks.
+
+* Added <tt>stored_attributes</tt> hash which contains the attributes stored using <tt>ActiveRecord::Store</tt>. This allows you to retrieve the list of attributes you've defined.
+
+<ruby>
+class User < ActiveRecord::Base
+ store :settings, accessors: [:color, :homepage]
+end
+
+User.stored_attributes[:settings] # [:color, :homepage]
+</ruby>
+
+* <tt>composed_of</tt> was removed. You'll have to write your own accessor and mutator methods if you'd like to use value objects to represent some portion of your models. So, instead of:
+
+<ruby>
+class Person < ActiveRecord::Base
+ composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
+end
+</ruby>
+
+you could write something like this:
+
+<ruby>
+def address
+ @address ||= Address.new(address_street, address_city)
+end
+
+def address=(address)
+ self[:address_street] = @address.street
+ self[:address_city] = @address.city
+
+ @address = address
+end
+</ruby>
+
+* PostgreSQL default log level is now 'warning', to bypass the noisy notice messages. You can change the log level using the <tt>min_messages</tt> option available in your <tt>config/database.yml</tt>.
+
+* Add uuid datatype support to PostgreSQL adapter.
+
+* <tt>update_attribute</tt> has been removed. Use <tt>update_column</tt> if you want to bypass mass-assignment protection, validations, callbacks, and touching of updated_at. Otherwise please use <tt>update_attributes</tt>.
+
+* Added <tt>ActiveRecord::Migration.check_pending!</tt> that raises an error if migrations are pending.
+
+* Added <tt>#destroy!</tt> which acts like <tt>#destroy</tt> but will raise an <tt>ActiveRecord::RecordNotDestroyed</tt> exception instead of returning <tt>false</tt>.
+
* Allow blocks for count with <tt>ActiveRecord::Relation</tt>, to work similar as <tt>Array#count</tt>: <tt>Person.where("age > 26").count { |person| person.gender == 'female' }</tt>
* Added support to <tt>CollectionAssociation#delete</tt> for passing fixnum or string values as record ids. This finds the records responding to the ids and deletes them.
@@ -355,7 +527,7 @@ Post.find_by! name: 'Spartacus'
* Added <tt>ActiveRecord::Base#slice</tt> to return a hash of the given methods with their names as keys and returned values as values.
-* Remove IdentityMap - IdentityMap has never graduated to be an "enabled-by-default" feature, due to some inconsistencies with associations, as described in this commit: https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. Hence the removal from the codebase, until such issues are fixed.
+* Remove IdentityMap - IdentityMap has never graduated to be an "enabled-by-default" feature, due to some inconsistencies with associations, as described in this "commit":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. Hence the removal from the codebase, until such issues are fixed.
* Added a feature to dump/load internal state of +SchemaCache+ instance because we want to boot more quickly when we have many models.
@@ -475,27 +647,37 @@ The code to implement the deprecated features has been moved out to the +active_
Don't use this:
- scope :red, where(color: 'red')
- default_scope where(color: 'red')
+<ruby>
+scope :red, where(color: 'red')
+default_scope where(color: 'red')
+</ruby>
Use this:
- scope :red, -> { where(color: 'red') }
- default_scope { where(color: 'red') }
+<ruby>
+scope :red, -> { where(color: 'red') }
+default_scope { where(color: 'red') }
+</ruby>
The former has numerous issues. It is a common newbie gotcha to do the following:
- scope :recent, where(published_at: Time.now - 2.weeks)
+<ruby>
+scope :recent, where(published_at: Time.now - 2.weeks)
+</ruby>
Or a more subtle variant:
- scope :recent, -> { where(published_at: Time.now - 2.weeks) }
- scope :recent_red, recent.where(color: 'red')
+<ruby>
+scope :recent, -> { where(published_at: Time.now - 2.weeks) }
+scope :recent_red, recent.where(color: 'red')
+</ruby>
Eager scopes are also very complex to implement within Active Record, and there are still bugs. For example, the following does not do what you expect:
- scope :remove_conditions, except(:where)
- where(...).remove_conditions # => still has conditions
+<ruby>
+scope :remove_conditions, except(:where)
+where(...).remove_conditions # => still has conditions
+</ruby>
* Added deprecation for the :dependent => :restrict association option.
@@ -507,6 +689,32 @@ The code to implement the deprecated features has been moved out to the +active_
h3. Active Model
+* Changed <tt>AM::Serializers::JSON.include_root_in_json</tt> default value to false. Now, AM Serializers and AR objects have the same default behaviour.
+
+<ruby>
+class User < ActiveRecord::Base; end
+
+class Person
+ include ActiveModel::Model
+ include ActiveModel::AttributeMethods
+ include ActiveModel::Serializers::JSON
+
+ attr_accessor :name, :age
+
+ def attributes
+ instance_values
+ end
+end
+
+user.as_json
+=> {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true}
+# root is not included
+
+person.as_json
+=> {"name"=>"Francesco", "age"=>22}
+# root is not included
+</ruby>
+
* Passing false hash values to +validates+ will no longer enable the corresponding validators.
* +ConfirmationValidator+ error messages will attach to <tt>:#{attribute}_confirmation</tt> instead of +attribute+.
@@ -521,10 +729,28 @@ h4(#activemodel_deprecations). Deprecations
h3. Active Resource
-* Active Resource is removed from Rails 4.0 and is now a separate gem. TODO: put a link to the gem here.
+* Active Resource is removed from Rails 4.0 and is now a separate "gem":https://github.com/rails/activeresource.
h3. Active Support
+* <tt>Time#change</tt> now works with time values with offsets other than UTC or the local time zone.
+
+* Add <tt>Time#prev_quarter</tt> and <tt>Time#next_quarter</tt> short-hands for <tt>months_ago(3)</tt> and <tt>months_since(3)</tt>.
+
+* Remove obsolete and unused <tt>require_association</tt> method from dependencies.
+
+* Add <tt>:instance_accessor</tt> option for <tt>config_accessor</tt>.
+
+<ruby>
+class User
+ include ActiveSupport::Configurable
+ config_accessor :allowed_access, instance_accessor: false
+end
+
+User.new.allowed_access = true # => NoMethodError
+User.new.allowed_access # => NoMethodError
+</ruby>
+
* <tt>ActionView::Helpers::NumberHelper</tt> methods have been moved to <tt>ActiveSupport::NumberHelper</tt> and are now available via <tt>Numeric#to_s</tt>.
* <tt>Numeric#to_s</tt> now accepts the formatting options :phone, :currency, :percentage, :delimited, :rounded, :human, and :human_size.
@@ -575,6 +801,8 @@ h3. Active Support
h4(#activesupport_deprecations). Deprecations
+* <tt>ActiveSupport::Callbacks</tt>: deprecate usage of filter object with <tt>#before</tt> and <tt>#after</tt> methods as <tt>around</tt> callback.
+
* <tt>BufferedLogger</tt> is deprecated. Use <tt>ActiveSupport::Logger</tt> or the +logger+ from Ruby stdlib.
* Deprecates the compatibility method <tt>Module#local_constant_names</tt> and use <tt>Module#local_constants</tt> instead (which returns symbols).
diff --git a/guides/source/action_mailer_basics.textile b/guides/source/action_mailer_basics.textile
index ebe774fbef..7c61cc4a8d 100644
--- a/guides/source/action_mailer_basics.textile
+++ b/guides/source/action_mailer_basics.textile
@@ -457,6 +457,7 @@ The following configuration options are best made in one of the environment file
|+delivery_method+|Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, <tt>:file</tt> and <tt>:test</tt>.|
|+perform_deliveries+|Determines whether deliveries are actually carried out when the +deliver+ method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.|
|+deliveries+|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.|
+|+async+|Setting this flag will turn on asynchronous message sending, message rendering and delivery will be pushed to <tt>Rails.queue</tt> for processing.|
h4. Example Action Mailer Configuration
@@ -514,3 +515,33 @@ end
</ruby>
In the test we send the email and store the returned object in the +email+ variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect.
+
+h3. Asynchronous
+
+You can turn on application-wide asynchronous message sending by adding to your <tt>config/application.rb</tt> file:
+
+<ruby>
+config.action_mailer.async = true
+</ruby>
+
+Alternatively you can turn on async within specific mailers:
+
+<ruby>
+class WelcomeMailer < ActionMailer::Base
+ self.async = true
+end
+</ruby>
+
+h4. Custom Queues
+
+If you need a different queue than <tt>Rails.queue</tt> for your mailer you can override <tt>ActionMailer::Base#queue</tt>:
+
+<ruby>
+class WelcomeMailer < ActionMailer::Base
+ def queue
+ MyQueue.new
+ end
+end
+</ruby>
+
+Your custom queue should expect a job that responds to <tt>#run</tt>.
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index af46538bf5..cd9aab4892 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -424,7 +424,7 @@ There are a number of settings available on +config.action_mailer+:
* +config.action_mailer.perform_deliveries+ specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing.
-* +config.action_mailer.default+ configures Action Mailer defaults. These default to:
+* +config.action_mailer.default_options+ configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to:
<ruby>
:mime_version => "1.0",
:charset => "UTF-8",
diff --git a/guides/source/debugging_rails_applications.textile b/guides/source/debugging_rails_applications.textile
index 0802a2db26..cc172042e9 100644
--- a/guides/source/debugging_rails_applications.textile
+++ b/guides/source/debugging_rails_applications.textile
@@ -102,7 +102,7 @@ It can also be useful to save information to log files at runtime. Rails maintai
h4. What is the Logger?
-Rails makes use of Ruby's standard +logger+ to write log information. You can also substitute another logger such as +Log4r+ if you wish.
+Rails makes use of the +ActiveSupport::BufferedLogger+ class to write log information. You can also substitute another logger such as +Log4r+ if you wish.
You can specify an alternative logger in your +environment.rb+ or any environment file:
diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile
index f25e0c0200..07419d11b4 100644
--- a/guides/source/getting_started.textile
+++ b/guides/source/getting_started.textile
@@ -20,13 +20,7 @@ application from scratch. It does not assume that you have any prior experience
with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
-* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.8.7 or higher
-
-TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails
-3.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02
-though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults
-on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 or
-1.9.3 for smooth sailing.
+* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.9.3 or higher
* The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system
** If you want to learn more about RubyGems, please read the "RubyGems User Guide":http://docs.rubygems.org/read/book/1
@@ -216,12 +210,12 @@ You need to do this because Rails will serve any static file in the +public+ dir
Next, you have to tell Rails where your actual home page is located.
-Open the file +config/routes.rb+ in your editor.
+Open the file +config/routes.rb+ in your editor.
<ruby>
Blog::Application.routes.draw do
get "welcome/index"
-
+
# The priority is based upon order of creation:
# first created -> highest priority.
# ...
@@ -906,7 +900,7 @@ end
</ruby>
The new method, +update_attributes+, is used when you want to update a record
-that already exists, and it accepts an hash containing the attributes
+that already exists, and it accepts a hash containing the attributes
that you want to update. As before, if there was an error updating the
post we want to show the form back to the user.
@@ -1191,7 +1185,7 @@ delete "posts/:id" => "posts#destroy"
That's a lot to type for covering a single *resource*. Fortunately,
Rails provides a +resources+ method which can be used to declare a
-standard REST resource. Here's how +config/routes/rb+ looks after the
+standard REST resource. Here's how +config/routes.rb+ looks after the
cleanup:
<ruby>
diff --git a/guides/source/performance_testing.textile b/guides/source/performance_testing.textile
index 958b13cd9e..982fd1b070 100644
--- a/guides/source/performance_testing.textile
+++ b/guides/source/performance_testing.textile
@@ -524,11 +524,11 @@ Please refer to the "API docs":http://api.rubyonrails.org/classes/ActiveRecord/B
h4. Controller
-Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715
+Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html
<ruby>
def process_projects
- self.class.benchmark("Processing projects") do
+ benchmark("Processing projects") do
Project.process(params[:project_ids])
Project.update_cached_projects
end
@@ -539,7 +539,7 @@ NOTE: +benchmark+ is a class method inside controllers
h4. View
-And in "views":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715:
+And in "views":http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html:
<erb>
<% benchmark("Showing projects partial") do %>
diff --git a/guides/source/routing.textile b/guides/source/routing.textile
index dae25853cd..cffbf9bec4 100644
--- a/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -32,7 +32,13 @@ the request is dispatched to the +patients+ controller's +show+ action with <tt>
h4. Generating Paths and URLs from Code
-You can also generate paths and URLs. If your application contains this code:
+You can also generate paths and URLs. If the route above is modified to be
+
+<ruby>
+get "/patients/:id" => "patients#show", :as => "patient"
+</ruby>
+
+If your application contains this code:
<ruby>
@patient = Patient.find(17)
@@ -845,24 +851,6 @@ end
This will create routing helpers such as +magazine_periodical_ads_url+ and +edit_magazine_periodical_ad_path+.
-h3. Breaking Up a Large Route File
-
-If you have a large route file that you would like to break up into multiple files, you can use the +#draw+ method in your router:
-
-<ruby>
-draw :admin
-</ruby>
-
-Then, create a file called +config/routes/admin.rb+. Name the file the same as the symbol passed to the +draw+ method. You can then use the normal routing DSL inside that file:
-
-<ruby>
-# in config/routes/admin.rb
-
-namespace :admin do
- resources :posts
-end
-</ruby>
-
h3. Inspecting and Testing Routes
Rails offers facilities for inspecting and testing your routes.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index e19aa68407..7223270210 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,15 @@
## Rails 4.0.0 (unreleased) ##
+* Allow scaffold/model/migration generators to accept a `polymorphic` modifier
+ for `references`/`belongs_to`, for instance
+
+ rails g model Product supplier:references{polymorphic}
+
+ will generate the model with `belongs_to :supplier, polymorphic: true`
+ association and appropriate migration.
+
+ *Aleksey Magusev*
+
* Set `config.active_record.migration_error` to `:page_load` for development *Richard Schneeman*
* Add runner to Rails::Railtie as a hook called just after runner starts. *José Valim & kennyj*
@@ -39,6 +49,9 @@
* Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino*
+* Set config.action_mailer.async = true to turn on asynchronous
+ message delivery *Brian Cardarella*
+
## Rails 3.2.2 (March 1, 2012) ##
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 32797ee657..d5ec2cbfd9 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -53,7 +53,6 @@ module Rails
autoload :Bootstrap, 'rails/application/bootstrap'
autoload :Configuration, 'rails/application/configuration'
autoload :Finisher, 'rails/application/finisher'
- autoload :Railties, 'rails/application/railties'
autoload :RoutesReloader, 'rails/application/routes_reloader'
class << self
@@ -80,9 +79,42 @@ module Rails
@routes_reloader = nil
@env_config = nil
@ordered_railties = nil
+ @railties = nil
@queue = nil
end
+ # Returns true if the application is initialized.
+ def initialized?
+ @initialized
+ end
+
+ # Implements call according to the Rack API. It simples
+ # dispatch the request to the underlying middleware stack.
+ def call(env)
+ env["ORIGINAL_FULLPATH"] = build_original_fullpath(env)
+ super(env)
+ end
+
+ # Reload application routes regardless if they changed or not.
+ def reload_routes!
+ routes_reloader.reload!
+ end
+
+ # Stores some of the Rails initial environment parameters which
+ # will be used by middlewares and engines to configure themselves.
+ def env_config
+ @env_config ||= super.merge({
+ "action_dispatch.parameter_filter" => config.filter_parameters,
+ "action_dispatch.secret_token" => config.secret_token,
+ "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
+ "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
+ "action_dispatch.logger" => Rails.logger,
+ "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
+ })
+ end
+
+ ## Rails internal API
+
# This method is called just after an application inherits from Rails::Application,
# allowing the developer to load classes in lib and use them during application
# configuration.
@@ -106,18 +138,14 @@ module Rails
require environment if environment
end
- # Reload application routes regardless if they changed or not.
- def reload_routes!
- routes_reloader.reload!
- end
-
def routes_reloader #:nodoc:
@routes_reloader ||= RoutesReloader.new
end
- # Returns an array of file paths appended with a hash of directories-extensions
- # suitable for ActiveSupport::FileUpdateChecker API.
- def watchable_args
+ # Returns an array of file paths appended with a hash of
+ # directories-extensions suitable for ActiveSupport::FileUpdateChecker
+ # API.
+ def watchable_args #:nodoc:
files, dirs = config.watchable_files.dup, config.watchable_dirs.dup
ActiveSupport::Dependencies.autoload_paths.each do |path|
@@ -138,45 +166,64 @@ module Rails
self
end
- def initialized?
- @initialized
+ def initializers #:nodoc:
+ Bootstrap.initializers_for(self) +
+ railties_initializers(super) +
+ Finisher.initializers_for(self)
end
- # Load the application and its railties tasks and invoke the registered hooks.
- # Check <tt>Rails::Railtie.rake_tasks</tt> for more info.
- def load_tasks(app=self)
- initialize_tasks
- super
+ def config #:nodoc:
+ @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd))
+ end
+
+ def queue #:nodoc:
+ @queue ||= build_queue
+ end
+
+ def build_queue #:nodoc:
+ config.queue.new
+ end
+
+ def to_app #:nodoc:
self
end
- # Load the application console and invoke the registered hooks.
- # Check <tt>Rails::Railtie.console</tt> for more info.
- def load_console(app=self)
- initialize_console
+ def helpers_paths #:nodoc:
+ config.helpers_paths
+ end
+
+ def railties #:nodoc:
+ @railties ||= Rails::Railtie.subclasses.map(&:instance) +
+ Rails::Engine.subclasses.map(&:instance)
+ end
+
+ protected
+
+ alias :build_middleware_stack :app
+
+ def run_tasks_blocks(app) #:nodoc:
+ railties.each { |r| r.run_tasks_blocks(app) }
+ super
+ require "rails/tasks"
+ task :environment do
+ $rails_rake_task = true
+ require_environment!
+ end
+ end
+
+ def run_generators_blocks(app) #:nodoc:
+ railties.each { |r| r.run_generators_blocks(app) }
super
- self
end
- # Load the application runner and invoke the registered hooks.
- # Check <tt>Rails::Railtie.runner</tt> for more info.
- def load_runner(app=self)
- initialize_runner
+ def run_runner_blocks(app) #:nodoc:
+ railties.each { |r| r.run_runner_blocks(app) }
super
- self
end
- # Stores some of the Rails initial environment parameters which
- # will be used by middlewares and engines to configure themselves.
- def env_config
- @env_config ||= super.merge({
- "action_dispatch.parameter_filter" => config.filter_parameters,
- "action_dispatch.secret_token" => config.secret_token,
- "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
- "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
- "action_dispatch.logger" => Rails.logger,
- "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
- })
+ def run_console_blocks(app) #:nodoc:
+ railties.each { |r| r.run_console_blocks(app) }
+ super
end
# Returns the ordered railties for this application considering railties_order.
@@ -192,7 +239,7 @@ module Rails
end
end
- all = (railties.all - order)
+ all = (railties - order)
all.push(self) unless (all + order).include?(self)
order.push(:all) unless order.include?(:all)
@@ -202,46 +249,23 @@ module Rails
end
end
- def initializers #:nodoc:
- Bootstrap.initializers_for(self) +
- super +
- Finisher.initializers_for(self)
- end
-
- def config #:nodoc:
- @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd))
- end
-
- def queue #:nodoc:
- @queue ||= build_queue
- end
-
- def build_queue # :nodoc:
- config.queue.new
- end
-
- def to_app
- self
- end
-
- def helpers_paths #:nodoc:
- config.helpers_paths
- end
-
- def call(env)
- env["ORIGINAL_FULLPATH"] = build_original_fullpath(env)
- super(env)
+ def railties_initializers(current) #:nodoc:
+ initializers = []
+ ordered_railties.each do |r|
+ if r == self
+ initializers += current
+ else
+ initializers += r.initializers
+ end
+ end
+ initializers
end
- protected
-
- alias :build_middleware_stack :app
-
- def reload_dependencies?
+ def reload_dependencies? #:nodoc:
config.reload_classes_only_on_change != true || reloaders.map(&:updated?).any?
end
- def default_middleware_stack
+ def default_middleware_stack #:nodoc:
ActionDispatch::MiddlewareStack.new.tap do |middleware|
if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache
require "action_dispatch/http/rack_cache"
@@ -296,26 +320,7 @@ module Rails
end
end
- def initialize_tasks #:nodoc:
- self.class.rake_tasks do
- require "rails/tasks"
- task :environment do
- $rails_rake_task = true
- require_environment!
- end
- end
- end
-
- def initialize_console #:nodoc:
- require "pp"
- require "rails/console/app"
- require "rails/console/helpers"
- end
-
- def initialize_runner #:nodoc:
- end
-
- def build_original_fullpath(env)
+ def build_original_fullpath(env) #:nodoc:
path_info = env["PATH_INFO"]
query_string = env["QUERY_STRING"]
script_name = env["SCRIPT_NAME"]
diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb
deleted file mode 100644
index f20a9689de..0000000000
--- a/railties/lib/rails/application/railties.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'rails/engine/railties'
-
-module Rails
- class Application < Engine
- class Railties < Rails::Engine::Railties
- def all(&block)
- @all ||= railties + engines
- @all.each(&block) if block
- @all
- end
- end
- end
-end
diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/routes_inspector.rb
index 942c4f4789..6b2caf8277 100644
--- a/railties/lib/rails/application/route_inspector.rb
+++ b/railties/lib/rails/application/routes_inspector.rb
@@ -62,7 +62,7 @@ module Rails
##
# This class is just used for displaying route information when someone
# executes `rake routes`. People should not use this class.
- class RouteInspector # :nodoc:
+ class RoutesInspector # :nodoc:
def initialize
@engines = Hash.new
end
diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb
index 19f616ec50..6f9a200aa9 100644
--- a/railties/lib/rails/application/routes_reloader.rb
+++ b/railties/lib/rails/application/routes_reloader.rb
@@ -3,13 +3,12 @@ require "active_support/core_ext/module/delegation"
module Rails
class Application
class RoutesReloader
- attr_reader :route_sets, :paths, :external_routes
+ attr_reader :route_sets, :paths
delegate :execute_if_updated, :execute, :updated?, :to => :updater
def initialize
- @paths = []
- @route_sets = []
- @external_routes = []
+ @paths = []
+ @route_sets = []
end
def reload!
@@ -24,11 +23,7 @@ module Rails
def updater
@updater ||= begin
- dirs = @external_routes.each_with_object({}) do |dir, hash|
- hash[dir.to_s] = %w(rb)
- end
-
- updater = ActiveSupport::FileUpdateChecker.new(paths, dirs) { reload! }
+ updater = ActiveSupport::FileUpdateChecker.new(paths) { reload! }
updater.execute
updater
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 806b553b81..383c159d3d 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -2,7 +2,6 @@ require 'rails/railtie'
require 'active_support/core_ext/module/delegation'
require 'pathname'
require 'rbconfig'
-require 'rails/engine/railties'
module Rails
# <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of
@@ -338,27 +337,6 @@ module Rails
# config.railties_order = [Blog::Engine, :main_app, :all]
class Engine < Railtie
autoload :Configuration, "rails/engine/configuration"
- autoload :Railties, "rails/engine/railties"
-
- def initialize
- @_all_autoload_paths = nil
- @_all_load_paths = nil
- @app = nil
- @config = nil
- @env_config = nil
- @helpers = nil
- @railties = nil
- @routes = nil
- super
- end
-
- def load_generators(app=self)
- initialize_generators
- railties.all { |r| r.load_generators(app) }
- Rails::Generators.configure!(app.config.generators)
- super
- self
- end
class << self
attr_accessor :called_from, :isolated
@@ -408,7 +386,7 @@ module Rails
end
unless mod.respond_to?(:railtie_routes_url_helpers)
- define_method(:railtie_routes_url_helpers) { railtie.routes_url_helpers }
+ define_method(:railtie_routes_url_helpers) { railtie.routes.url_helpers }
end
end
end
@@ -417,34 +395,65 @@ module Rails
# Finds engine with given path
def find(path)
expanded_path = File.expand_path path
- Rails::Engine::Railties.engines.find { |engine|
- File.expand_path(engine.root) == expanded_path
- }
+ Rails::Engine.subclasses.each do |klass|
+ engine = klass.instance
+ return engine if File.expand_path(engine.root) == expanded_path
+ end
+ nil
end
end
delegate :middleware, :root, :paths, :to => :config
delegate :engine_name, :isolated?, :to => "self.class"
- def load_tasks(app=self)
- railties.all { |r| r.load_tasks(app) }
+ def initialize
+ @_all_autoload_paths = nil
+ @_all_load_paths = nil
+ @app = nil
+ @config = nil
+ @env_config = nil
+ @helpers = nil
+ @routes = nil
super
- paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end
+ # Load console and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.console</tt> for more info.
def load_console(app=self)
- railties.all { |r| r.load_console(app) }
- super
+ require "pp"
+ require "rails/console/app"
+ require "rails/console/helpers"
+ run_console_blocks(app)
+ self
end
+ # Load Rails runner and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.runner</tt> for more info.
def load_runner(app=self)
- railties.all { |r| r.load_runner(app) }
- super
+ run_runner_blocks(app)
+ self
end
- def eager_load!
- railties.all(&:eager_load!)
+ # Load Rake, railties tasks and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.rake_tasks</tt> for more info.
+ def load_tasks(app=self)
+ require "rake"
+ run_tasks_blocks(app)
+ self
+ end
+ # Load rails generators and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.generators</tt> for more info.
+ def load_generators(app=self)
+ require "rails/generators"
+ run_generators_blocks(app)
+ Rails::Generators.configure!(app.config.generators)
+ self
+ end
+
+ # Eager load the application by loading all ruby
+ # files inside eager_load paths.
+ def eager_load!
config.eager_load_paths.each do |load_path|
matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
@@ -453,10 +462,7 @@ module Rails
end
end
- def railties
- @railties ||= self.class::Railties.new(config)
- end
-
+ # Returns a module with all the helpers defined for the engine.
def helpers
@helpers ||= begin
helpers = Module.new
@@ -468,14 +474,12 @@ module Rails
end
end
+ # Returns all registered helpers paths.
def helpers_paths
paths["app/helpers"].existent
end
- def routes_url_helpers
- routes.url_helpers
- end
-
+ # Returns the underlying rack application for this engine.
def app
@app ||= begin
config.middleware = config.middleware.merge_into(default_middleware_stack)
@@ -483,45 +487,33 @@ module Rails
end
end
+ # Returns the endpoint for this engine. If none is registered,
+ # defaults to an ActionDispatch::Routing::RouteSet.
def endpoint
self.class.endpoint || routes
end
+ # Define the Rack API for this engine.
def call(env)
app.call(env.merge!(env_config))
end
+ # Defines additional Rack env configuration that is added on each call.
def env_config
@env_config ||= {
'action_dispatch.routes' => routes
}
end
+ # Defines the routes for this engine. If a block is given to
+ # routes, it is appended to the engine.
def routes
- @routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes|
- routes.draw_paths.concat paths["config/routes"].paths
- end
-
+ @routes ||= ActionDispatch::Routing::RouteSet.new
@routes.append(&Proc.new) if block_given?
@routes
end
- def ordered_railties
- railties.all + [self]
- end
-
- def initializers
- initializers = []
- ordered_railties.each do |r|
- if r == self
- initializers += super
- else
- initializers += r.initializers
- end
- end
- initializers
- end
-
+ # Define the configuration object for the engine.
def config
@config ||= Engine::Configuration.new(find_root_with_flag("lib"))
end
@@ -560,12 +552,10 @@ module Rails
initializer :add_routing_paths do |app|
paths = self.paths["config/routes.rb"].existent
- external_paths = self.paths["config/routes"].paths
if routes? || paths.any?
app.routes_reloader.paths.unshift(*paths)
app.routes_reloader.route_sets << routes
- app.routes_reloader.external_routes.unshift(*external_paths)
end
end
@@ -626,7 +616,6 @@ module Rails
else
Rake::Task["app:railties:install:migrations"].invoke
end
-
end
end
end
@@ -634,19 +623,20 @@ module Rails
protected
- def initialize_generators
- require "rails/generators"
+ def run_tasks_blocks(*) #:nodoc:
+ super
+ paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end
- def routes?
+ def routes? #:nodoc:
@routes
end
- def has_migrations?
+ def has_migrations? #:nodoc:
paths["db/migrate"].existent.any?
end
- def find_root_with_flag(flag, default=nil)
+ def find_root_with_flag(flag, default=nil) #:nodoc:
root_path = self.class.called_from
while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}")
@@ -660,19 +650,19 @@ module Rails
Pathname.new File.realpath root
end
- def default_middleware_stack
+ def default_middleware_stack #:nodoc:
ActionDispatch::MiddlewareStack.new
end
- def _all_autoload_once_paths
+ def _all_autoload_once_paths #:nodoc:
config.autoload_once_paths
end
- def _all_autoload_paths
+ def _all_autoload_paths #:nodoc:
@_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq
end
- def _all_load_paths
+ def _all_load_paths #:nodoc:
@_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq
end
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index e31df807a6..6b18b1e249 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -53,7 +53,6 @@ module Rails
paths.add "config/initializers", :glob => "**/*.rb"
paths.add "config/locales", :glob => "*.{rb,yml}"
paths.add "config/routes.rb"
- paths.add "config/routes", :glob => "**/*.rb"
paths.add "db"
paths.add "db/migrate"
paths.add "db/seeds.rb"
diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb
deleted file mode 100644
index 033d9c4180..0000000000
--- a/railties/lib/rails/engine/railties.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module Rails
- class Engine < Railtie
- class Railties
- # TODO Write tests for this behavior extracted from Application
- def initialize(config)
- @config = config
- end
-
- def all(&block)
- @all ||= []
- @all.each(&block) if block
- @all
- end
-
- def self.railties
- @railties ||= ::Rails::Railtie.subclasses.map(&:instance)
- end
-
- def self.engines
- @engines ||= ::Rails::Engine.subclasses.map(&:instance)
- end
-
- delegate :railties, :engines, :to => "self.class"
- end
- end
-end
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 25d0161e4c..d480fc12b5 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -35,7 +35,7 @@ module Rails
private
- # parse possible attribute options like :limit for string/text/binary/integer or :precision/:scale for decimals
+ # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to
# when declaring options curly brackets should be used
def parse_type_and_options(type)
case type
@@ -43,6 +43,8 @@ module Rails
return $1, :limit => $2.to_i
when /decimal\{(\d+)[,.-](\d+)\}/
return :decimal, :precision => $1.to_i, :scale => $2.to_i
+ when /(references|belongs_to)\{polymorphic\}/
+ return $1, :polymorphic => true
else
return type, {}
end
@@ -92,13 +94,21 @@ module Rails
end
def index_name
- reference? ? "#{name}_id" : name
+ if reference?
+ polymorphic? ? %w(id type).map { |t| "#{name}_#{t}" } : "#{name}_id"
+ else
+ name
+ end
end
def reference?
self.class.reference?(type)
end
+ def polymorphic?
+ self.attr_options.has_key?(:polymorphic)
+ end
+
def has_index?
@has_index
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 2b8e5b5dcd..5915d20010 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -67,5 +67,8 @@ module <%= app_const_base %>
# Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.0'
<% end -%>
+
+ # Enable app-wide asynchronous ActionMailer
+ # config.action_mailer.async = true
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index 0090293200..9afda2d0df 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -6,7 +6,7 @@ class ActiveSupport::TestCase
<% unless options[:skip_active_record] -%>
ActiveRecord::Migration.check_pending!
- # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 67f76aad01..c46c86076e 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -19,6 +19,55 @@ Description:
then the generator will create a module with a table_name_prefix method
to prefix the model's table name with the module name (e.g. admin_account)
+Available field types:
+
+ Just after the field name you can specify a type like text or boolean.
+ It will generate the column with the associated SQL type. For instance:
+
+ `rails generate model post title:string body:text`
+
+ will generate a title column with a varchar type and a body column with a text
+ type. You can use the following types:
+
+ integer
+ primary_key
+ decimal
+ float
+ boolean
+ binary
+ string
+ text
+ date
+ time
+ datetime
+ timestamp
+
+ You can also consider `references` as a kind of type. For instance, if you run:
+
+ `rails generate model photo title:string album:references`
+
+ It will generate an album_id column. You should generate this kind of fields when
+ you will use a `belongs_to` association for instance. `references` also support
+ the polymorphism, you could enable the polymorphism like this:
+
+ `rails generate model product supplier:references{polymorphic}`
+
+ You can also specify some options just after the field type. You can use the
+ following options:
+
+ limit Set the maximum size of the field giving a number between curly braces
+ default Set a default value for the field
+ precision Defines the precision for the decimal fields
+ scale Defines the scale for the decimal fields
+ uniq Defines the field values as unique
+ index Will add an index on the field
+
+ Examples:
+
+ `rails generate model user pseudo:string{30}`
+ `rails generate model user pseudo:string:uniq`
+
+
Examples:
`rails generate model account`
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore
index 458b2c662e..086d87818a 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore
@@ -1,8 +1,10 @@
.bundle/
log/*.log
pkg/
+<% unless options[:skip_test_unit] && options[:dummy_path] == 'test/dummy' -%>
<%= dummy_path %>/db/*.sqlite3
<%= dummy_path %>/db/*.sqlite3-journal
<%= dummy_path %>/log/*.log
<%= dummy_path %>/tmp/
<%= dummy_path %>/.sass-cache
+<% end -%> \ No newline at end of file
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 5081074395..bacdcbf3aa 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -1,4 +1,4 @@
-require 'rails/application/route_inspector'
+require 'rails/application/routes_inspector'
class Rails::InfoController < ActionController::Base
self.view_paths = File.join(File.dirname(__FILE__), 'templates')
@@ -15,7 +15,7 @@ class Rails::InfoController < ActionController::Base
end
def routes
- inspector = Rails::Application::RouteInspector.new
+ inspector = Rails::Application::RoutesInspector.new
@info = inspector.format(_routes.routes).join("\n")
end
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 6cd9c7bc95..3c2210aaf9 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -51,7 +51,8 @@ module Rails
end
def []=(path, value)
- add(path, :with => value)
+ glob = self[path] ? self[path].glob : nil
+ add(path, :with => value, :glob => glob)
end
def add(path, options={})
@@ -114,7 +115,7 @@ module Rails
class Path
include Enumerable
- attr_reader :path, :root
+ attr_reader :path
attr_accessor :glob
def initialize(root, current, paths, options = {})
@@ -180,14 +181,6 @@ module Rails
@paths
end
- def paths
- raise "You need to set a path root" unless @root.path
-
- map do |p|
- File.join @root.path, p
- end
- end
-
# Expands all paths against the root and return all unique values.
def expanded
raise "You need to set a path root" unless @root.path
diff --git a/railties/lib/rails/queueing.rb b/railties/lib/rails/queueing.rb
index b4bc7fcd18..8a76914548 100644
--- a/railties/lib/rails/queueing.rb
+++ b/railties/lib/rails/queueing.rb
@@ -22,6 +22,13 @@ module Rails
@que.dup
end
+ # Marshal and unmarshal job before pushing it onto the queue. This will
+ # raise an exception on any attempts in tests to push jobs that can't (or
+ # shouldn't) be marshalled.
+ def push(job)
+ super Marshal.load(Marshal.dump(job))
+ end
+
# Drain the queue, running all jobs in a different thread. This method
# may not be available on production queues.
def drain
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index a06be59759..1cb99463cc 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -181,33 +181,35 @@ module Rails
def eager_load!
end
- def load_console(app=self)
+ def railtie_namespace
+ @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) }
+ end
+
+ protected
+
+ def run_console_blocks(app) #:nodoc:
self.class.console.each { |block| block.call(app) }
end
- def load_runner(app=self)
+ def run_generators_blocks(app) #:nodoc:
+ self.class.generators.each { |block| block.call(app) }
+ end
+
+ def run_runner_blocks(app) #:nodoc:
self.class.runner.each { |block| block.call(app) }
end
- def load_tasks(app=self)
- require 'rake'
+ def run_tasks_blocks(app) #:nodoc:
extend Rake::DSL
- self.class.rake_tasks.each { |block| self.instance_exec(app, &block) }
+ self.class.rake_tasks.each { |block| instance_exec(app, &block) }
- # load also tasks from all superclasses
+ # Load also tasks from all superclasses
klass = self.class.superclass
+
while klass.respond_to?(:rake_tasks)
- klass.rake_tasks.each { |t| self.instance_exec(app, &t) }
+ klass.rake_tasks.each { |t| instance_exec(app, &t) }
klass = klass.superclass
end
end
-
- def load_generators(app=self)
- self.class.generators.each { |block| block.call(app) }
- end
-
- def railtie_namespace
- @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) }
- end
end
end
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 5778b22f18..4ade825616 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -1,7 +1,7 @@
desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task :routes => :environment do
all_routes = Rails.application.routes.routes
- require 'rails/application/route_inspector'
- inspector = Rails::Application::RouteInspector.new
+ require 'rails/application/routes_inspector'
+ inspector = Rails::Application::RoutesInspector.new
puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n"
end
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 46bf3bbe48..581ceaf9ce 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -10,7 +10,10 @@ require 'action_dispatch/testing/integration'
# Enable turn if it is available
begin
require 'turn'
- MiniTest::Unit.use_natural_language_case_names = true
+
+ Turn.config do |c|
+ c.natural = true
+ end
rescue LoadError
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 207739c4bc..6d28947e83 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
s.rdoc_options << '--exclude' << '.'
s.add_dependency('rake', '>= 0.8.7')
- s.add_dependency('thor', '>= 0.15.3', '< 2.0')
+ s.add_dependency('thor', '>= 0.15.4', '< 2.0')
s.add_dependency('rdoc', '~> 3.4')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb
index e0893f53be..4029984ce9 100644
--- a/railties/test/application/paths_test.rb
+++ b/railties/test/application/paths_test.rb
@@ -50,8 +50,6 @@ module ApplicationTests
assert_path @paths["config/locales"], "config/locales/en.yml"
assert_path @paths["config/environment"], "config/environment.rb"
assert_path @paths["config/environments"], "config/environments/development.rb"
- assert_path @paths["config/routes.rb"], "config/routes.rb"
- assert_path @paths["config/routes"], "config/routes"
assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first
end
diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb
index da8bdeed52..22d6c20404 100644
--- a/railties/test/application/queue_test.rb
+++ b/railties/test/application/queue_test.rb
@@ -30,46 +30,68 @@ module ApplicationTests
assert_kind_of Rails::Queueing::Queue, Rails.queue
end
- test "in development mode, an enqueued job will be processed in a separate thread" do
- app("development")
+ class ThreadTrackingJob
+ def initialize
+ @origin = Thread.current.object_id
+ end
+
+ def run
+ @target = Thread.current.object_id
+ end
- job = Struct.new(:origin, :target).new(Thread.current)
- def job.run
- self.target = Thread.current
+ def ran_in_different_thread?
+ @origin != @target
end
+ def ran?
+ @target
+ end
+ end
+
+ test "in development mode, an enqueued job will be processed in a separate thread" do
+ app("development")
+
+ job = ThreadTrackingJob.new
Rails.queue.push job
sleep 0.1
- assert job.target, "The job was run"
- assert_not_equal job.origin, job.target
+ assert job.ran?, "Expected job to be run"
+ assert job.ran_in_different_thread?, "Expected job to run in a different thread"
end
test "in test mode, explicitly draining the queue will process it in a separate thread" do
app("test")
- job = Struct.new(:origin, :target).new(Thread.current)
- def job.run
- self.target = Thread.current
+ Rails.queue.push ThreadTrackingJob.new
+ job = Rails.queue.jobs.last
+ Rails.queue.drain
+
+ assert job.ran?, "Expected job to be run"
+ assert job.ran_in_different_thread?, "Expected job to run in a different thread"
+ end
+
+ class IdentifiableJob
+ def initialize(id)
+ @id = id
end
- Rails.queue.push job
- Rails.queue.drain
+ def ==(other)
+ other.same_id?(@id)
+ end
+
+ def same_id?(other_id)
+ other_id == @id
+ end
- assert job.target, "The job was run"
- assert_not_equal job.origin, job.target
+ def run
+ end
end
test "in test mode, the queue can be observed" do
app("test")
- job = Struct.new(:id) do
- def run
- end
- end
-
jobs = (1..10).map do |id|
- job.new(id)
+ IdentifiableJob.new(id)
end
jobs.each do |job|
@@ -79,6 +101,29 @@ module ApplicationTests
assert_equal jobs, Rails.queue.jobs
end
+ test "in test mode, adding an unmarshallable job will raise an exception" do
+ app("test")
+ anonymous_class_instance = Struct.new(:run).new
+ assert_raises TypeError do
+ Rails.queue.push anonymous_class_instance
+ end
+ end
+
+ test "attempting to marshal a queue will raise an exception" do
+ app("test")
+ assert_raises TypeError do
+ Marshal.dump Rails.queue
+ end
+ end
+
+ test "attempting to add a reference to itself to the queue will raise an exception" do
+ app("test")
+ job = {reference: Rails.queue}
+ assert_raises TypeError do
+ Rails.queue.push job
+ end
+ end
+
def setup_custom_queue
add_to_env_config "production", <<-RUBY
require "my_queue"
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index e0ee349550..a450f90dbf 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -144,6 +144,24 @@ module ApplicationTests
assert_no_match(/Errors running/, output)
end
+ def test_db_test_clone_when_using_sql_format
+ add_to_config "config.active_record.schema_format = :sql"
+ output = Dir.chdir(app_path) do
+ `rails generate scaffold user username:string;
+ bundle exec rake db:migrate db:test:clone 2>&1 --trace`
+ end
+ assert_match(/Execute db:test:clone_structure/, output)
+ end
+
+ def test_db_test_prepare_when_using_sql_format
+ add_to_config "config.active_record.schema_format = :sql"
+ output = Dir.chdir(app_path) do
+ `rails generate scaffold user username:string;
+ bundle exec rake db:migrate db:test:prepare 2>&1 --trace`
+ end
+ assert_match(/Execute db:test:load_structure/, output)
+ end
+
def test_rake_dump_structure_should_respect_db_structure_env_variable
Dir.chdir(app_path) do
# ensure we have a schema_migrations table to dump
diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/routes_inspect_test.rb
index 3b8c874b5b..68a8afc93e 100644
--- a/railties/test/application/route_inspect_test.rb
+++ b/railties/test/application/routes_inspect_test.rb
@@ -1,13 +1,13 @@
require 'minitest/autorun'
-require 'rails/application/route_inspector'
+require 'rails/application/routes_inspector'
require 'action_controller'
require 'rails/engine'
module ApplicationTests
- class RouteInspectTest < ActiveSupport::TestCase
+ class RoutesInspectTest < ActiveSupport::TestCase
def setup
@set = ActionDispatch::Routing::RouteSet.new
- @inspector = Rails::Application::RouteInspector.new
+ @inspector = Rails::Application::RoutesInspector.new
app = ActiveSupport::OrderedOptions.new
app.config = ActiveSupport::OrderedOptions.new
app.config.assets = ActiveSupport::OrderedOptions.new
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index d1373ba202..396b1849d8 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -178,90 +178,7 @@ module ApplicationTests
assert_equal 'WIN', last_response.body
end
- test "routes drawing from config/routes" do
- app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
- draw :external
- end
- RUBY
-
- app_file 'config/routes/external.rb', <<-RUBY
- get ':controller/:action'
- RUBY
-
- controller :success, <<-RUBY
- class SuccessController < ActionController::Base
- def index
- render :text => "success!"
- end
- end
- RUBY
-
- app 'development'
- get '/success/index'
- assert_equal 'success!', last_response.body
- end
-
{"development" => "baz", "production" => "bar"}.each do |mode, expected|
- test "reloads routes when external configuration is changed in #{mode}" do
- controller :foo, <<-RUBY
- class FooController < ApplicationController
- def bar
- render :text => "bar"
- end
-
- def baz
- render :text => "baz"
- end
- end
- RUBY
-
- app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
- draw :external
- end
- RUBY
-
- app_file 'config/routes/external.rb', <<-RUBY
- get 'foo', :to => 'foo#bar'
- RUBY
-
- app(mode)
-
- get '/foo'
- assert_equal 'bar', last_response.body
-
- app_file 'config/routes/external.rb', <<-RUBY
- get 'foo', :to => 'foo#baz'
- RUBY
-
- sleep 0.1
-
- get '/foo'
- assert_equal expected, last_response.body
-
- app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do
- draw :external
- draw :other_external
- end
- RUBY
-
- app_file 'config/routes/other_external.rb', <<-RUBY
- get 'win', :to => 'foo#baz'
- RUBY
-
- sleep 0.1
-
- get '/win'
-
- if mode == "development"
- assert_equal expected, last_response.body
- else
- assert_equal 404, last_response.status
- end
- end
-
test "reloads routes when configuration is changed in #{mode}" do
controller :foo, <<-RUBY
class FooController < ApplicationController
diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb
index 68406dce4c..addf49cdb6 100644
--- a/railties/test/engine_test.rb
+++ b/railties/test/engine_test.rb
@@ -11,14 +11,4 @@ class EngineTest < ActiveSupport::TestCase
assert !engine.routes?
end
-
- it "does not add more paths to routes on each call" do
- engine = Class.new(Rails::Engine)
-
- engine.routes
- length = engine.routes.draw_paths.length
-
- engine.routes
- assert_equal length, engine.routes.draw_paths.length
- end
end
diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb
index 6e3fc84781..6ab1cd58c7 100644
--- a/railties/test/generators/generated_attribute_test.rb
+++ b/railties/test/generators/generated_attribute_test.rb
@@ -102,22 +102,28 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
def test_reference_is_true
%w(references belongs_to).each do |attribute_type|
- assert_equal(
- true,
- create_generated_attribute(attribute_type).reference?
- )
+ assert create_generated_attribute(attribute_type).reference?
end
end
def test_reference_is_false
%w(foo bar baz).each do |attribute_type|
- assert_equal(
- false,
- create_generated_attribute(attribute_type).reference?
- )
+ assert !create_generated_attribute(attribute_type).reference?
end
end
+ def test_polymorphic_reference_is_true
+ %w(references belongs_to).each do |attribute_type|
+ assert create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic?
+ end
+ end
+
+ def test_polymorphic_reference_is_false
+ %w(foo bar baz).each do |attribute_type|
+ assert !create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic?
+ end
+ end
+
def test_blank_type_defaults_to_string_raises_exception
assert_equal :string, create_generated_attribute(nil, 'title').type
assert_equal :string, create_generated_attribute("", 'title').type
@@ -126,5 +132,6 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
def test_handles_index_names_for_references
assert_equal "post", create_generated_attribute('string', 'post').index_name
assert_equal "post_id", create_generated_attribute('references', 'post').index_name
+ assert_equal ["post_id", "post_type"], create_generated_attribute('references{polymorphic}', 'post').index_name
end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index fd3b8c8a17..ec33bd7c6b 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -166,7 +166,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
end
def test_add_migration_with_attributes_index_declaration_and_attribute_options
- run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq"]
+ run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq", "supplier:references{polymorphic}"]
assert_migration "db/migrate/create_products.rb" do |content|
assert_method :change, content do |up|
@@ -174,6 +174,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_match(/t.string :title, limit: 40/, up)
assert_match(/t.string :content, limit: 255/, up)
assert_match(/t.decimal :price, precision: 5, scale: 2/, up)
+ assert_match(/t.references :supplier, polymorphic: true/, up)
end
assert_match(/add_index :products, :title/, content)
assert_match(/add_index :products, :price/, content)
@@ -193,15 +194,25 @@ class ModelGeneratorTest < Rails::Generators::TestCase
end
def test_model_with_references_attribute_generates_belongs_to_associations
- run_generator ["product", "name:string", "supplier_id:references"]
+ run_generator ["product", "name:string", "supplier:references"]
assert_file "app/models/product.rb", /belongs_to :supplier/
end
def test_model_with_belongs_to_attribute_generates_belongs_to_associations
- run_generator ["product", "name:string", "supplier_id:belongs_to"]
+ run_generator ["product", "name:string", "supplier:belongs_to"]
assert_file "app/models/product.rb", /belongs_to :supplier/
end
+ def test_model_with_polymorphic_references_attribute_generates_belongs_to_associations
+ run_generator ["product", "name:string", "supplier:references{polymorphic}"]
+ assert_file "app/models/product.rb", /belongs_to :supplier, polymorphic: true/
+ end
+
+ def test_model_with_polymorphic_belongs_to_attribute_generates_belongs_to_associations
+ run_generator ["product", "name:string", "supplier:belongs_to{polymorphic}"]
+ assert_file "app/models/product.rb", /belongs_to :supplier, polymorphic: true/
+ end
+
def test_migration_with_timestamps
run_generator
assert_migration "db/migrate/create_accounts.rb", /t.timestamps/
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 2ae9dc61a7..db2b8af217 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -99,7 +99,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
run_generator ["admin/account"]
assert_file "app/models/test_app/admin.rb", /module TestApp/, /module Admin/
assert_file "app/models/test_app/admin.rb", /def self\.table_name_prefix/
- assert_file "app/models/test_app/admin.rb", /'admin_'/
+ assert_file "app/models/test_app/admin.rb", /'test_app_admin_'/
assert_file "app/models/test_app/admin/account.rb", /module TestApp/, /class Admin::Account < ActiveRecord::Base/
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 0a235b56d5..6c3e0178f2 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -264,11 +264,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "spec/dummy"
assert_file "spec/dummy/config/application.rb"
assert_no_file "test"
+ assert_file '.gitignore' do |contents|
+ assert_match(/spec\/dummy/, contents)
+ end
end
def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path
FileUtils.cd(Rails.root)
- run_generator([destination_root, "--dummy_path", "spec/dummy" "--skip-test-unit"])
+ run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"])
assert_file ".gitignore" do |contents|
assert_match(/spec\/dummy/, contents)
end
@@ -280,6 +283,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "bukkits.gemspec" do |contents|
assert_no_match(/s.test_files = Dir\["test\/\*\*\/\*"\]/, contents)
end
+ assert_file '.gitignore' do |contents|
+ assert_no_match(/test\dummy/, contents)
+ end
end
def test_skipping_gemspec
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 417d019178..ad2f85a5ff 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/model/model_generator'
require 'rails/generators/test_unit/model/model_generator'
-require 'mocha'
class GeneratorsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 800b1c90f0..6071cd3f39 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -273,24 +273,18 @@ end
# Create a scope and build a fixture rails app
Module.new do
extend TestHelpers::Paths
+
# Build a rails app
- if File.exist?(app_template_path)
- FileUtils.rm_rf(app_template_path)
- end
+ FileUtils.rm_rf(app_template_path)
FileUtils.mkdir(app_template_path)
environment = File.expand_path('../../../../load_paths', __FILE__)
- if File.exist?("#{environment}.rb")
- require_environment = "-r #{environment}"
- end
+ require_environment = "-r #{environment}"
`#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path}`
+
File.open("#{app_template_path}/config/boot.rb", 'w') do |f|
- if require_environment
- f.puts "Dir.chdir('#{File.dirname(environment)}') do"
- f.puts " require '#{environment}'"
- f.puts "end"
- end
+ f.puts "require '#{environment}'"
f.puts "require 'rails/all'"
end
end unless defined?(RAILS_ISOLATED_ENGINE)
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 5d6b6f9f72..76ff3ec3e4 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -29,7 +29,6 @@ class PathsTest < ActiveSupport::TestCase
test "creating a root level path" do
@root.add "app"
assert_equal ["/foo/bar/app"], @root["app"].to_a
- assert_equal ["/foo/bar/app"], @root["app"].paths
end
test "creating a root level path with options" do
@@ -192,7 +191,6 @@ class PathsTest < ActiveSupport::TestCase
@root["app"] = "/app"
@root["app"].glob = "*.rb"
assert_equal "*.rb", @root["app"].glob
- assert_equal ["/foo/bar/app"], @root["app"].paths
end
test "it should be possible to override a path's default glob without assignment" do
@@ -200,6 +198,13 @@ class PathsTest < ActiveSupport::TestCase
assert_equal "*.rb", @root["app"].glob
end
+ test "it should be possible to replace a path and persist the original paths glob" do
+ @root.add "app", :glob => "*.rb"
+ @root["app"] = "app2"
+ assert_equal ["/foo/bar/app2"], @root["app"].to_a
+ assert_equal "*.rb", @root["app"].glob
+ end
+
test "a path can be added to the load path" do
@root["app"] = "app"
@root["app"].load_path!
diff --git a/railties/test/queueing/test_queue_test.rb b/railties/test/queueing/test_queue_test.rb
index 78c6c617fe..2f0f507adb 100644
--- a/railties/test/queueing/test_queue_test.rb
+++ b/railties/test/queueing/test_queue_test.rb
@@ -2,22 +2,18 @@ require 'abstract_unit'
require 'rails/queueing'
class TestQueueTest < ActiveSupport::TestCase
- class Job
- def initialize(&block)
- @block = block
- end
+ def setup
+ @queue = Rails::Queueing::TestQueue.new
+ end
+ class ExceptionRaisingJob
def run
- @block.call if @block
+ raise
end
end
- def setup
- @queue = Rails::Queueing::TestQueue.new
- end
-
def test_drain_raises
- @queue.push Job.new { raise }
+ @queue.push ExceptionRaisingJob.new
assert_raises(RuntimeError) { @queue.drain }
end
@@ -27,41 +23,80 @@ class TestQueueTest < ActiveSupport::TestCase
assert_equal [1,2], @queue.jobs
end
+ class EquivalentJob
+ def initialize
+ @initial_id = self.object_id
+ end
+
+ def run
+ end
+
+ def ==(other)
+ other.same_initial_id?(@initial_id)
+ end
+
+ def same_initial_id?(other_id)
+ other_id == @initial_id
+ end
+ end
+
def test_contents
assert @queue.empty?
- job = Job.new
+ job = EquivalentJob.new
@queue.push job
refute @queue.empty?
assert_equal job, @queue.pop
end
- def test_order
- processed = []
+ class ProcessingJob
+ def self.clear_processed
+ @processed = []
+ end
+
+ def self.processed
+ @processed
+ end
+
+ def initialize(object)
+ @object = object
+ end
+
+ def run
+ self.class.processed << @object
+ end
+ end
- job1 = Job.new { processed << 1 }
- job2 = Job.new { processed << 2 }
+ def test_order
+ ProcessingJob.clear_processed
+ job1 = ProcessingJob.new(1)
+ job2 = ProcessingJob.new(2)
@queue.push job1
@queue.push job2
@queue.drain
- assert_equal [1,2], processed
+ assert_equal [1,2], ProcessingJob.processed
end
- def test_drain
- t = nil
- ran = false
+ class ThreadTrackingJob
+ attr_reader :thread_id
- job = Job.new do
- ran = true
- t = Thread.current
+ def run
+ @thread_id = Thread.current.object_id
end
- @queue.push job
+ def ran?
+ @thread_id
+ end
+ end
+
+ def test_drain
+ @queue.push ThreadTrackingJob.new
+ job = @queue.jobs.last
@queue.drain
assert @queue.empty?
- assert ran, "The job runs synchronously when the queue is drained"
- assert_not_equal t, Thread.current
+ assert job.ran?, "The job runs synchronously when the queue is drained"
+ assert_not_equal job.thread_id, Thread.current.object_id
end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 52c7fae6c6..63814f7a04 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1098,7 +1098,7 @@ YAML
assert_equal "// App's bar js\n;", last_response.body.strip
# ensure that railties are not added twice
- railties = Rails.application.ordered_railties.map(&:class)
+ railties = Rails.application.send(:ordered_railties).map(&:class)
assert_equal railties, railties.uniq
end
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index 1938bfb6c2..c90b795d59 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -75,6 +75,18 @@ module RailtiesTests
end
end
+ def test_table_name_prefix_is_correctly_namespaced_when_engine_is_mountable
+ build_mountable_engine
+ Dir.chdir(engine_path) do
+ bundled_rails("g model namespaced/topic")
+ assert_file "app/models/foo_bar/namespaced.rb", /module FooBar\n module Namespaced/ do |content|
+ assert_class_method :table_name_prefix, content do |method_content|
+ assert_match(/'foo_bar_namespaced_'/, method_content)
+ end
+ end
+ end
+ end
+
def test_helpers_are_correctly_namespaced_when_engine_is_mountable
build_mountable_engine
Dir.chdir(engine_path) do