aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml6
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Gemfile2
-rw-r--r--actionmailer/lib/action_mailer/base.rb2
-rw-r--r--actionpack/CHANGELOG.md12
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/metal.rb8
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb10
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb11
-rw-r--r--actionpack/lib/action_controller/renderer.rb100
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb7
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb5
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb9
-rw-r--r--actionpack/test/controller/renderer_test.rb103
-rw-r--r--actionpack/test/dispatch/routing_test.rb38
-rw-r--r--actionview/CHANGELOG.md5
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb5
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb127
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb11
-rw-r--r--actionview/lib/action_view/model_naming.rb2
-rw-r--r--actionview/lib/action_view/record_identifier.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb7
-rw-r--r--actionview/test/actionpack/controller/render_test.rb9
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb24
-rw-r--r--actionview/test/template/form_helper_test.rb28
-rw-r--r--activemodel/CHANGELOG.md44
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_assignment.rb63
-rw-r--r--activemodel/lib/active_model/dirty.rb1
-rw-r--r--activemodel/lib/active_model/errors.rb61
-rw-r--r--activemodel/lib/active_model/naming.rb1
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb8
-rw-r--r--activemodel/test/cases/attribute_assignment_test.rb107
-rw-r--r--activemodel/test/cases/errors_test.rb42
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb6
-rw-r--r--activerecord/CHANGELOG.md77
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc6
-rw-r--r--activerecord/lib/active_record/attribute.rb14
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb53
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb33
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb33
-rw-r--r--activerecord/lib/active_record/attribute_set.rb4
-rw-r--r--activerecord/lib/active_record/attributes.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/core.rb37
-rw-r--r--activerecord/lib/active_record/errors.rb16
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb16
-rw-r--r--activerecord/lib/active_record/relation.rb1
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb28
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb51
-rw-r--r--activerecord/lib/active_record/secure_token.rb2
-rw-r--r--activerecord/lib/active_record/table_metadata.rb6
-rw-r--r--activerecord/lib/active_record/type/integer.rb22
-rw-r--r--activerecord/lib/active_record/type/time.rb13
-rw-r--r--activerecord/lib/active_record/type/time_value.rb4
-rw-r--r--activerecord/lib/active_record/type/value.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb1
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/required_test.rb4
-rw-r--r--activerecord/test/cases/attribute_decorators_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb75
-rw-r--r--activerecord/test/cases/attribute_set_test.rb11
-rw-r--r--activerecord/test/cases/attribute_test.rb19
-rw-r--r--activerecord/test/cases/attributes_test.rb2
-rw-r--r--activerecord/test/cases/callbacks_test.rb21
-rw-r--r--activerecord/test/cases/date_time_test.rb2
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb8
-rw-r--r--activerecord/test/cases/helper.rb3
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb13
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb2
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb4
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb4
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb4
-rw-r--r--activerecord/test/cases/migration/table_and_index_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb2
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb4
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb10
-rw-r--r--activerecord/test/cases/primary_keys_test.rb2
-rw-r--r--activerecord/test/cases/quoting_test.rb13
-rw-r--r--activerecord/test/cases/relation/predicate_builder_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_test.rb8
-rw-r--r--activerecord/test/cases/relations_test.rb6
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb36
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb25
-rw-r--r--activerecord/test/cases/type/integer_test.rb39
-rw-r--r--activerecord/test/cases/type/unsigned_integer_test.rb4
-rw-r--r--activerecord/test/cases/types_test.rb17
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb2
-rw-r--r--activesupport/CHANGELOG.md12
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb2
-rw-r--r--activesupport/lib/active_support/duration.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb138
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb14
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb5
-rw-r--r--activesupport/test/core_ext/duration_test.rb9
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb8
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb3
-rw-r--r--guides/assets/images/getting_started/article_with_comments.pngbin15190 -> 22560 bytes
-rw-r--r--guides/bug_report_templates/active_record_gem.rb4
-rw-r--r--guides/bug_report_templates/active_record_master.rb4
-rw-r--r--guides/rails_guides/markdown/renderer.rb2
-rw-r--r--guides/source/2_2_release_notes.md2
-rw-r--r--guides/source/2_3_release_notes.md2
-rw-r--r--guides/source/3_0_release_notes.md2
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/4_0_release_notes.md2
-rw-r--r--guides/source/4_1_release_notes.md2
-rw-r--r--guides/source/4_2_release_notes.md2
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md2
-rw-r--r--guides/source/action_view_overview.md2
-rw-r--r--guides/source/active_job_basics.md20
-rw-r--r--guides/source/active_model_basics.md6
-rw-r--r--guides/source/active_record_basics.md4
-rw-r--r--guides/source/active_record_callbacks.md2
-rw-r--r--guides/source/active_record_migrations.md2
-rw-r--r--guides/source/active_record_postgresql.md2
-rw-r--r--guides/source/active_record_querying.md12
-rw-r--r--guides/source/active_record_validations.md62
-rw-r--r--guides/source/active_support_core_extensions.md66
-rw-r--r--guides/source/active_support_instrumentation.md2
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/asset_pipeline.md4
-rw-r--r--guides/source/association_basics.md2
-rw-r--r--guides/source/autoloading_and_reloading_constants.md31
-rw-r--r--guides/source/caching_with_rails.md2
-rw-r--r--guides/source/command_line.md2
-rw-r--r--guides/source/configuring.md4
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md4
-rw-r--r--guides/source/debugging_rails_applications.md2
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/engines.md2
-rw-r--r--guides/source/form_helpers.md6
-rw-r--r--guides/source/generators.md2
-rw-r--r--guides/source/getting_started.md25
-rw-r--r--guides/source/i18n.md7
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/layouts_and_rendering.md16
-rw-r--r--guides/source/maintenance_policy.md2
-rw-r--r--guides/source/nested_model_forms.md2
-rw-r--r--guides/source/plugins.md2
-rw-r--r--guides/source/profiling.md2
-rw-r--r--guides/source/rails_application_templates.md2
-rw-r--r--guides/source/rails_on_rack.md2
-rw-r--r--guides/source/routing.md2
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md2
-rw-r--r--guides/source/security.md3
-rw-r--r--guides/source/testing.md65
-rw-r--r--guides/source/upgrading_ruby_on_rails.md2
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--railties/CHANGELOG.md4
-rw-r--r--railties/lib/rails/application/configuration.rb17
-rw-r--r--railties/lib/rails/commands/dbconsole.rb4
-rw-r--r--railties/lib/rails/engine.rb6
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css5
-rw-r--r--railties/lib/rails/railtie.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb7
-rw-r--r--railties/test/isolation/abstract_unit.rb3
201 files changed, 1753 insertions, 716 deletions
diff --git a/.travis.yml b/.travis.yml
index e4ed59d4a6..d686f6de03 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,7 @@ language: ruby
sudo: false
script: 'ci/travis.rb'
before_install:
- - "rvm current | grep 'jruby' && export AR_JDBC=true || echo"
+ - gem install bundler
before_script:
- bundle update
cache: bundler
@@ -23,12 +23,12 @@ rvm:
- 2.2
- ruby-head
- rbx-2
- - jruby
+ - jruby-head
matrix:
allow_failures:
- env: "GEM=ar:mysql"
- rvm: rbx-2
- - rvm: jruby
+ - rvm: jruby-head
- env: "GEM=aj"
- env: "GEM=aj:integration"
fast_finish: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9ba2e53ef2..421e7088cb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea
* If you want to contribute to Rails documentation, please read the [Contributing to the Rails Documentation](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation) section of the aforementioned guide.
-*We only accept bug reports and pull requests in GitHub*.
+*We only accept bug reports and pull requests on GitHub*.
* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
diff --git a/Gemfile b/Gemfile
index eb0a324ab2..0bcfab7b79 100644
--- a/Gemfile
+++ b/Gemfile
@@ -64,7 +64,7 @@ group :test do
gem 'ruby-prof', '~> 0.11.2'
end
- platforms :mri_21 do
+ platforms :mri_21, :mri_22 do
gem 'stackprof'
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 53cc1fdb31..7eae76f93b 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -58,7 +58,7 @@ module ActionMailer
#
# The mail method, if not passed a block, will inspect your views and send all the views with
# the same name as the method, so the above action would send the +welcome.text.erb+ view
- # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email.
+ # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email.
#
# If you want to explicitly render only certain templates, pass a block:
#
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 6cd0b2d15d..d545c59afc 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,15 @@
+* Add `ActionController::Renderer` to render arbitrary templates
+ outside controller actions.
+
+ Its functionality is accessible through class methods `render` and
+ `renderer` of `ActionController::Base`.
+
+ *Ravil Bayramgalin*
+
+* Support `:assigns` option when rendering with controllers/mailers.
+
+ *Ravil Bayramgalin*
+
* Default headers, removed in controller actions, are no longer reapplied on
the test response.
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index f83823dd75..d9b23ad4a9 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
- s.add_dependency 'rack', '~> 1.6.0'
+ s.add_dependency 'rack', '~> 1.6'
s.add_dependency 'rack-test', '~> 0.6.2'
s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1'
s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index e977bbce99..7667e469d3 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -11,6 +11,7 @@ module ActionController
autoload :Caching
autoload :Metal
autoload :Middleware
+ autoload :Renderer
autoload_under "metal" do
autoload :Compatibility
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 993f8e150d..ae111e4951 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -190,11 +190,15 @@ module ActionController
end
def dispatch(name, request) #:nodoc:
+ set_request!(request)
+ process(name)
+ to_a
+ end
+
+ def set_request!(request) #:nodoc:
@_request = request
@_env = request.env
@_env['action_controller.instance'] = self
- process(name)
- to_a
end
def to_a #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index b210ee3423..febbc72861 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -51,7 +51,7 @@ module ActionController
#
# def show
# @article = Article.find(params[:id])
- # fresh_when(etag: @article, last_modified: @article.created_at, public: true)
+ # fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
# end
#
# This will render the show template if the request isn't sending a matching ETag or
@@ -115,7 +115,7 @@ module ActionController
# def show
# @article = Article.find(params[:id])
#
- # if stale?(etag: @article, last_modified: @article.created_at)
+ # if stale?(etag: @article, last_modified: @article.updated_at)
# @statistics = @article.really_expensive_call
# respond_to do |format|
# # all the supported formats
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index 545d4a7e6e..ae9d89cc8c 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -8,9 +8,15 @@ module ActionController
delegate :headers, :status=, :location=, :content_type=,
:status, :location, :content_type, :response_code, :to => "@_response"
- def dispatch(action, request)
+ module ClassMethods
+ def build_with_env(env = {}) #:nodoc:
+ new.tap { |c| c.set_request! ActionDispatch::Request.new(env) }
+ end
+ end
+
+ def set_request!(request) #:nodoc:
+ super
set_response!(request)
- super(action, request)
end
def response_body=(body)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 7bbff0450a..2d15c39d88 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -4,6 +4,17 @@ module ActionController
RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+ module ClassMethods
+ # Documentation at ActionController::Renderer#render
+ delegate :render, to: :renderer
+
+ # Returns a renderer class (inherited from ActionController::Renderer)
+ # for the controller.
+ def renderer
+ @renderer ||= Renderer.for(self)
+ end
+ end
+
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
self.formats = request.formats.map(&:ref).compact
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
new file mode 100644
index 0000000000..e8b29c5b5e
--- /dev/null
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -0,0 +1,100 @@
+require 'active_support/core_ext/hash/keys'
+
+module ActionController
+ # ActionController::Renderer allows to render arbitrary templates
+ # without requirement of being in controller actions.
+ #
+ # You get a concrete renderer class by invoking ActionController::Base#renderer.
+ # For example,
+ #
+ # ApplicationController.renderer
+ #
+ # It allows you to call method #render directly.
+ #
+ # ApplicationController.renderer.render template: '...'
+ #
+ # You can use a shortcut on controller to replace previous example with:
+ #
+ # ApplicationController.render template: '...'
+ #
+ # #render method allows you to use any options as when rendering in controller.
+ # For example,
+ #
+ # FooController.render :action, locals: { ... }, assigns: { ... }
+ #
+ # The template will be rendered in a Rack environment which is accessible through
+ # ActionController::Renderer#env. You can set it up in two ways:
+ #
+ # * by changing renderer defaults, like
+ #
+ # ApplicationController.renderer.defaults # => hash with default Rack environment
+ #
+ # * by initializing an instance of renderer by passing it a custom environment.
+ #
+ # ApplicationController.renderer.new(method: 'post', https: true)
+ #
+ class Renderer
+ class_attribute :controller, :defaults
+ # Rack environment to render templates in.
+ attr_reader :env
+
+ class << self
+ delegate :render, to: :new
+
+ # Create a new renderer class for a specific controller class.
+ def for(controller)
+ Class.new self do
+ self.controller = controller
+ self.defaults = {
+ http_host: 'example.org',
+ https: false,
+ method: 'get',
+ script_name: '',
+ 'rack.input' => ''
+ }
+ end
+ end
+ end
+
+ # Accepts a custom Rack environment to render templates in.
+ # It will be merged with ActionController::Renderer.defaults
+ def initialize(env = {})
+ @env = normalize_keys(defaults).merge normalize_keys(env)
+ @env['action_dispatch.routes'] = controller._routes
+ end
+
+ # Render templates with any options from ActionController::Base#render_to_string.
+ def render(*args)
+ raise 'missing controller' unless controller?
+
+ instance = controller.build_with_env(env)
+ instance.render_to_string(*args)
+ end
+
+ private
+ def normalize_keys(env)
+ http_header_format(env).tap do |new_env|
+ handle_method_key! new_env
+ handle_https_key! new_env
+ end
+ end
+
+ def http_header_format(env)
+ env.transform_keys do |key|
+ key.is_a?(Symbol) ? key.to_s.upcase : key
+ end
+ end
+
+ def handle_method_key!(env)
+ if method = env.delete('METHOD')
+ env['REQUEST_METHOD'] = method.upcase
+ end
+ end
+
+ def handle_https_key!(env)
+ if env.has_key? 'HTTPS'
+ env['HTTPS'] = env['HTTPS'] ? 'on' : 'off'
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 63a3cbc90b..747d295261 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -69,17 +69,17 @@ module ActionDispatch
end
def date
- if date_header = headers['Date']
+ if date_header = headers[DATE]
Time.httpdate(date_header)
end
end
def date?
- headers.include?('Date')
+ headers.include?(DATE)
end
def date=(utc_time)
- headers['Date'] = utc_time.httpdate
+ headers[DATE] = utc_time.httpdate
end
def etag=(etag)
@@ -89,6 +89,7 @@ module ActionDispatch
private
+ DATE = 'Date'.freeze
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 8b04dfaa45..f2c9e7b1a0 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1739,9 +1739,10 @@ module ActionDispatch
member_name = parent_resource.member_name
end
- name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
+ action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
+ candidate = action_name.select(&:present?).join('_')
- if candidate = name.compact.join("_").presence
+ unless candidate.empty?
# If a name was not explicitly given, we check if it is valid
# and return nil in case it isn't. Otherwise, we pass the invalid name
# forward so the underlying router engine treats it and raises an exception.
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index 246ba099af..710c428dcc 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -31,6 +31,15 @@ module BareMetalTest
controller.index
assert_equal ["Hello world"], controller.response_body
end
+
+ test "connect a request to controller instance without dispatch" do
+ env = {}
+ controller = BareController.new
+ controller.set_request! ActionDispatch::Request.new(env)
+ assert controller.request
+ assert controller.response
+ assert env['action_controller.instance']
+ end
end
class HeadController < ActionController::Metal
diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb
new file mode 100644
index 0000000000..6d5508323b
--- /dev/null
+++ b/actionpack/test/controller/renderer_test.rb
@@ -0,0 +1,103 @@
+require 'abstract_unit'
+
+class RendererTest < ActiveSupport::TestCase
+ test 'creating with a controller' do
+ controller = CommentsController
+ renderer = ActionController::Renderer.for controller
+
+ assert_equal controller, renderer.controller
+ end
+
+ test 'creating from a controller' do
+ controller = AccountsController
+ renderer = controller.renderer
+
+ assert_equal controller, renderer.controller
+ end
+
+ test 'rendering with a class renderer' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'ruby_template'
+
+ assert_equal 'Hello from Ruby code', content
+ end
+
+ test 'rendering with an instance renderer' do
+ renderer = ApplicationController.renderer.new
+ content = renderer.render file: 'test/hello_world'
+
+ assert_equal 'Hello world!', content
+ end
+
+ test 'rendering with a controller class' do
+ assert_equal 'Hello world!', ApplicationController.render('test/hello_world')
+ end
+
+ test 'rendering with locals' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'test/render_file_with_locals',
+ locals: { secret: 'bar' }
+
+ assert_equal "The secret is bar\n", content
+ end
+
+ test 'rendering with assigns' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'test/render_file_with_ivar',
+ assigns: { secret: 'foo' }
+
+ assert_equal "The secret is foo\n", content
+ end
+
+ test 'rendering with custom env' do
+ renderer = ApplicationController.renderer.new method: 'post'
+ content = renderer.render inline: '<%= request.post? %>'
+
+ assert_equal 'true', content
+ end
+
+ test 'rendering with defaults' do
+ renderer = ApplicationController.renderer
+ renderer.defaults[:https] = true
+ content = renderer.render inline: '<%= request.ssl? %>'
+
+ assert_equal 'true', content
+ end
+
+ test 'same defaults from the same controller' do
+ defaults = ->(controller) { controller.renderer.defaults }
+
+ assert defaults[AccountsController].equal? defaults[AccountsController]
+ assert_not defaults[AccountsController].equal? defaults[CommentsController]
+ end
+
+ test 'rendering with different formats' do
+ html = 'Hello world!'
+ xml = "<p>Hello world!</p>\n"
+
+ assert_equal html, render['respond_to/using_defaults']
+ assert_equal xml, render['respond_to/using_defaults.xml.builder']
+ assert_equal xml, render['respond_to/using_defaults', formats: :xml]
+ end
+
+ test 'rendering with helpers' do
+ assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>']
+ end
+
+ test 'rendering from inherited renderer' do
+ inherited = Class.new ApplicationController.renderer do
+ defaults[:script_name] = 'script'
+ def render(options)
+ super options.merge(locals: { param: :value })
+ end
+ end
+
+ template = '<%= url_for controller: :foo, action: :bar, param: param %>'
+ assert_equal 'script/foo/bar?param=value', inherited.render(inline: template)
+ end
+
+ private
+ def render
+ @render ||= ApplicationController.renderer.method(:render)
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 450681c356..3373705326 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3439,6 +3439,44 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/bar/comments/1', comment_path('1')
end
+ def test_resource_where_as_is_empty
+ draw do
+ resource :post, as: ''
+
+ scope 'post', as: 'post' do
+ resource :comment, as: ''
+ end
+ end
+
+ assert_equal '/post/new', new_path
+ assert_equal '/post/comment/new', new_post_path
+ end
+
+ def test_resources_where_as_is_empty
+ draw do
+ resources :posts, as: ''
+
+ scope 'posts', as: 'posts' do
+ resources :comments, as: ''
+ end
+ end
+
+ assert_equal '/posts/new', new_path
+ assert_equal '/posts/comments/new', new_posts_path
+ end
+
+ def test_scope_where_as_is_empty
+ draw do
+ scope 'post', as: '' do
+ resource :user
+ resources :comments
+ end
+ end
+
+ assert_equal '/post/user/new', new_user_path
+ assert_equal '/post/comments/new', new_comment_path
+ end
+
private
def draw(&block)
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index ab3cb7eb19..743c01e393 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,6 +1,6 @@
* Change the default template handler from `ERB` to `Raw`.
- Files without a template handler in their extension will be rended using the raw
+ Files without a template handler in their extension will be rendered using the raw
handler instead of ERB.
*Rafael Mendonça França*
@@ -28,5 +28,8 @@
*Angelo Capilleri*
+* Allow entries without a link tag in AtomFeedHelper.
+
+ *Daniel Gomez de Souza*
Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index 227ad4cdfa..d8be4e5678 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -174,7 +174,7 @@ module ActionView
#
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
- # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
+ # * <tt>:url</tt>: The URL for this entry or false or nil for not having a link tag. Defaults to the polymorphic_url for the record.
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
# * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
def entry(record, options = {})
@@ -191,7 +191,8 @@ module ActionView
type = options.fetch(:type, 'text/html')
- @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
+ url = options.fetch(:url) { @view.polymorphic_url(record) }
+ @xml.link(:rel => 'alternate', :type => type, :href => url) if url
yield AtomBuilder.new(@xml)
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 93c04fbec6..8bb243b8b7 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -777,10 +777,10 @@ module ActionView
# # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
#
# number_field_tag 'quantity', nil, min: 1, max: 10
- # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ # # => <input id="quantity" name="quantity" min="1" max="10" type="number" />
#
# number_field_tag 'quantity', nil, min: 1, max: 10, step: 2
- # # => <input id="quantity" name="quantity" min="1" max="9" step="2" type="number" />
+ # # => <input id="quantity" name="quantity" min="1" max="10" step="2" type="number" />
#
# number_field_tag 'quantity', '1', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="quantity" name="quantity" type="number" value="1" />
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index e72e85ee5f..463a4e9f60 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -8,76 +8,77 @@ module ActionView
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
extend ActiveSupport::Concern
- # This +sanitize+ helper will HTML encode all tags and strip all attributes that
- # aren't specifically allowed.
+ # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted.
#
- # It also strips href/src tags with invalid protocols, like javascript: especially.
- # It does its best to counter any tricks that hackers may use, like throwing in
- # unicode/ascii/hex values to get past the javascript: filters. Check out
- # the extensive test suite.
+ # It also strips href/src attributes with unsafe protocols like
+ # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
+ # ASCII, and hex character references to work around these protocol filters.
#
- # <%= sanitize @article.body %>
+ # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML
+ # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
#
- # You can add or remove tags/attributes if you want to customize it a bit.
- # See ActionView::Base for full docs on the available options. You can add
- # tags/attributes for single uses of +sanitize+ by passing either the
- # <tt>:attributes</tt> or <tt>:tags</tt> options:
+ # Custom sanitization rules can also be provided.
#
- # Normal Use
- #
- # <%= sanitize @article.body %>
+ # Please note that sanitizing user-provided text does not guarantee that the
+ # resulting markup is valid or even well-formed. For example, the output may still
+ # contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>.
#
- # Custom Use - Custom Scrubber
- # (supply a Loofah::Scrubber that does the sanitization)
+ # ==== Options
#
- # scrubber can either wrap a block:
- # scrubber = Loofah::Scrubber.new do |node|
- # node.text = "dawn of cats"
- # end
+ # * <tt>:tags</tt> - An array of allowed tags.
+ # * <tt>:attributes</tt> - An array of allowed attributes.
+ # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
+ # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
+ # defines custom sanitization rules. A custom scrubber takes precedence over
+ # custom tags and attributes.
#
- # or be a subclass of Loofah::Scrubber which responds to scrub:
- # class KittyApocalypse < Loofah::Scrubber
- # def scrub(node)
- # node.text = "dawn of cats"
- # end
- # end
- # scrubber = KittyApocalypse.new
+ # ==== Examples
#
- # <%= sanitize @article.body, scrubber: scrubber %>
+ # Normal use:
#
- # A custom scrubber takes precedence over custom tags and attributes
- # Learn more about scrubbers here: https://github.com/flavorjones/loofah
+ # <%= sanitize @comment.body %>
#
- # Custom Use - tags and attributes
- # (only the mentioned tags and attributes are allowed, nothing else)
+ # Providing custom whitelisted tags and attributes:
#
- # <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %>
+ # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
#
- # Add table tags to the default allowed tags
+ # Providing a custom Rails::Html scrubber:
#
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
- # end
+ # class CommentScrubber < Rails::Html::PermitScrubber
+ # def allowed_node?(node)
+ # !%w(form script comment blockquote).include?(node.name)
+ # end
#
- # Remove tags to the default allowed tags
+ # def skip_node?(node)
+ # node.text?
+ # end
#
- # class Application < Rails::Application
- # config.after_initialize do
- # ActionView::Base.sanitized_allowed_tags.delete 'div'
+ # def scrub_attribute?(name)
+ # name == 'style'
# end
# end
#
- # Change allowed default attributes
+ # <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
+ #
+ # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
+ # documentation about Rails::Html scrubbers.
#
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style']
+ # Providing a custom Loofah::Scrubber:
+ #
+ # scrubber = Loofah::Scrubber.new do |node|
+ # node.remove if node.name == 'script'
# end
#
- # Please note that sanitizing user-provided text does not guarantee that the
- # resulting markup is valid (conforming to a document type) or even well-formed.
- # The output may still contain e.g. unescaped '<', '>', '&' characters and
- # confuse browsers.
+ # <%= sanitize @comment.body, scrubber: scrubber %>
+ #
+ # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
+ # information about defining custom Loofah::Scrubber objects.
#
+ # To set the default allowed tags or attributes across your application:
+ #
+ # # In config/application.rb
+ # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
+ # config.action_view.sanitized_allowed_attributes = ['href', 'title']
def sanitize(html, options = {})
self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
end
@@ -87,9 +88,7 @@ module ActionView
self.class.white_list_sanitizer.sanitize_css(style)
end
- # Strips all HTML tags from the +html+, including comments. This uses
- # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability
- # is limited by that of Nokogiri.
+ # Strips all HTML tags from +html+, including comments.
#
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
@@ -103,7 +102,7 @@ module ActionView
self.class.full_sanitizer.sanitize(html)
end
- # Strips all link tags from +text+ leaving just the link text.
+ # Strips all link tags from +html+ leaving just the link text.
#
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
# # => Ruby on Rails
@@ -166,30 +165,6 @@ module ActionView
def white_list_sanitizer
@white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
end
-
- ##
- # :method: sanitized_allowed_tags=
- #
- # :call-seq: sanitized_allowed_tags=(tags)
- #
- # Replaces the allowed tags for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
- # end
- #
-
- ##
- # :method: sanitized_allowed_attributes=
- #
- # :call-seq: sanitized_allowed_attributes=(attributes)
- #
- # Replaces the allowed HTML attributes for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']
- # end
- #
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index f8abb19698..7740c60eac 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -32,12 +32,19 @@ module ActionView
unless object.nil?
method_before_type_cast = @method_name + "_before_type_cast"
- object.respond_to?(method_before_type_cast) ?
- object.send(method_before_type_cast) :
+ if value_came_from_user?(object) && object.respond_to?(method_before_type_cast)
+ object.public_send(method_before_type_cast)
+ else
value(object)
+ end
end
end
+ def value_came_from_user?(object)
+ method_name = "#{@method_name}_came_from_user?"
+ !object.respond_to?(method_name) || object.public_send(method_name)
+ end
+
def retrieve_object(object)
if object
object
diff --git a/actionview/lib/action_view/model_naming.rb b/actionview/lib/action_view/model_naming.rb
index d42e436b17..b6ed13424e 100644
--- a/actionview/lib/action_view/model_naming.rb
+++ b/actionview/lib/action_view/model_naming.rb
@@ -1,5 +1,5 @@
module ActionView
- module ModelNaming
+ module ModelNaming #:nodoc:
# Converts the given object to an ActiveModel compliant one.
def convert_to_model(object)
object.respond_to?(:to_model) ? object.to_model : object
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index c8484bed34..6c6e69101b 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -103,7 +103,7 @@ module ActionView
# make sure yourself that your dom ids are valid, in case you overwrite this method.
def record_key_for_dom_id(record)
key = convert_to_model(record).to_key
- key ? key.join('_') : key
+ key ? key.join(JOIN) : key
end
end
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index abd3b77c67..1e8e7415d1 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -92,12 +92,15 @@ module ActionView
# Find and render a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
- variant = options[:variant]
+ variant = options.delete(:variant)
+ assigns = options.delete(:assigns)
+ context = view_context
+ context.assign assigns if assigns
lookup_context.rendered_format = nil if options[:formats]
lookup_context.variants = variant if variant
- view_renderer.render(view_context, options)
+ view_renderer.render(context, options)
end
# Assign the rendered format to lookup context.
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 563caee8a2..94f519e330 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -453,6 +453,10 @@ class TestController < ApplicationController
render :text => "foo"
end
+ def render_with_assigns_option
+ render inline: '<%= @hello %>', assigns: { hello: "world" }
+ end
+
def yield_content_for
render :action => "content_for", :layout => "yield"
end
@@ -1102,6 +1106,11 @@ class RenderTest < ActionController::TestCase
assert_equal "world", assigns["hello"]
end
+ def test_render_text_with_assigns_option
+ get :render_with_assigns_option
+ assert_equal 'world', response.body
+ end
+
# :ported:
def test_template_with_locals
get :render_with_explicit_template_with_locals
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index 68b44c4f0d..9e06621c83 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -62,6 +62,23 @@ class ScrollsController < ActionController::Base
end
end
EOT
+ FEEDS["entry_url_false_option"] = <<-EOT
+ atom_feed do |feed|
+ feed.title("My great blog!")
+ feed.updated((@scrolls.first.created_at))
+
+ @scrolls.each do |scroll|
+ feed.entry(scroll, :url => false) do |entry|
+ entry.title(scroll.title)
+ entry.content(scroll.body, :type => 'html')
+
+ entry.author do |author|
+ author.name("DHH")
+ end
+ end
+ end
+ end
+ EOT
FEEDS["xml_block"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
@@ -337,6 +354,13 @@ class AtomFeedTest < ActionController::TestCase
end
end
+ def test_feed_entry_url_false_option_adds_no_link
+ with_restful_routing(:scrolls) do
+ get :index, :id => 'entry_url_false_option'
+ assert_select "entry link", false
+ end
+ end
+
private
def with_restful_routing(resources)
with_routing do |set|
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index fff1e1e572..dccf6a2090 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -99,7 +99,9 @@ class FormHelperTest < ActionView::TestCase
}.new
end
def @post.to_key; [123]; end
- def @post.id_before_type_cast; 123; end
+ def @post.id; 0; end
+ def @post.id_before_type_cast; "omg"; end
+ def @post.id_came_from_user?; true; end
def @post.to_param; '123'; end
@post.persisted = true
@@ -900,9 +902,29 @@ class FormHelperTest < ActionView::TestCase
)
end
- def test_text_area_with_value_before_type_cast
+ def test_inputs_use_before_type_cast_to_retain_information_from_validations_like_numericality
assert_dom_equal(
- %{<textarea id="post_id" name="post[id]">\n123</textarea>},
+ %{<textarea id="post_id" name="post[id]">\nomg</textarea>},
+ text_area("post", "id")
+ )
+ end
+
+ def test_inputs_dont_use_before_type_cast_when_value_did_not_come_from_user
+ class << @post
+ undef id_came_from_user?
+ def id_came_from_user?; false; end
+ end
+
+ assert_dom_equal(
+ %{<textarea id="post_id" name="post[id]">\n0</textarea>},
+ text_area("post", "id")
+ )
+ end
+
+ def test_inputs_use_before_typecast_when_object_doesnt_respond_to_came_from_user
+ class << @post; undef id_came_from_user?; end
+ assert_dom_equal(
+ %{<textarea id="post_id" name="post[id]">\nomg</textarea>},
text_area("post", "id")
)
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index f86b4804c8..9cebb680fc 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,47 @@
+* Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment`
+ allowing to use it for any object as an includable module
+
+ ``` ruby
+ class Cat
+ include ActiveModel::AttributeAssignment
+ attr_accessor :name, :status
+ end
+
+ cat = Cat.new
+ cat.assign_attributes(name: "Gorby", status: "yawning")
+ cat.name # => 'Gorby'
+ cat.status => 'yawning'
+ cat.assign_attributes(status: "sleeping")
+ cat.name # => 'Gorby'
+ cat.status => 'sleeping'
+ ```
+
+ *Bogdan Gusiev*
+
+* Add `ActiveModel::Errors#details`
+
+ To be able to return type of used validator, one can now call `details`
+ on Errors instance:
+
+ ```ruby
+ class User < ActiveRecord::Base
+ validates :name, presence: true
+ end
+ ```
+
+ ```ruby
+ user = User.new; user.valid?; user.errors.details
+ => {name: [{error: :blank}]}
+ ```
+
+ *Wojciech Wnętrzak*
+
+* Change validates_acceptance_of to accept true by default.
+
+ The default for validates_acceptance_of is now "1" and true.
+ In the past, only "1" was the default and you were required to add
+ accept: true.
+
* Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and
`ActiveModel::Dirty#reset_changes`.
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 46d60db756..58fe08cc11 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -28,6 +28,7 @@ require 'active_model/version'
module ActiveModel
extend ActiveSupport::Autoload
+ autoload :AttributeAssignment
autoload :AttributeMethods
autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb
new file mode 100644
index 0000000000..356421476c
--- /dev/null
+++ b/activemodel/lib/active_model/attribute_assignment.rb
@@ -0,0 +1,63 @@
+require 'active_support/core_ext/hash/keys'
+
+module ActiveModel
+ module AttributeAssignment
+ include ActiveModel::ForbiddenAttributesProtection
+
+ # Allows you to set all the attributes by passing in a hash of attributes with
+ # keys matching the attribute names.
+ #
+ # If the passed hash responds to <tt>permitted?</tt> method and the return value
+ # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
+ # exception is raised.
+ #
+ # class Cat
+ # include ActiveModel::AttributeAssignment
+ # attr_accessor :name, :status
+ # end
+ #
+ # cat = Cat.new
+ # cat.assign_attributes(name: "Gorby", status: "yawning")
+ # cat.name # => 'Gorby'
+ # cat.status => 'yawning'
+ # cat.assign_attributes(status: "sleeping")
+ # cat.name # => 'Gorby'
+ # cat.status => 'sleeping'
+ def assign_attributes(new_attributes)
+ if !new_attributes.respond_to?(:stringify_keys)
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
+ end
+ return if new_attributes.blank?
+
+ attributes = new_attributes.stringify_keys
+ _assign_attributes(sanitize_for_mass_assignment(attributes))
+ end
+
+ private
+
+ def _assign_attributes(attributes)
+ attributes.each do |k, v|
+ _assign_attribute(k, v)
+ end
+ end
+
+ def _assign_attribute(k, v)
+ if respond_to?("#{k}=")
+ public_send("#{k}=", v)
+ else
+ raise UnknownAttributeError.new(self, k)
+ end
+ end
+
+ # Raised when unknown attributes are supplied via mass assignment.
+ class UnknownAttributeError < NoMethodError
+ attr_reader :record, :attribute
+
+ def initialize(record, attribute)
+ @record = record
+ @attribute = attribute
+ super("unknown attribute '#{attribute}' for #{@record.class}.")
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index afba9bab0d..614bc6a5d8 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -189,6 +189,7 @@ module ActiveModel
def changes_include?(attr_name)
attributes_changed_by_setter.include?(attr_name)
end
+ alias attribute_changed_by_setter? changes_include?
# Removes current changes and makes them accessible through +previous_changes+.
def changes_applied # :doc:
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 55687cb3c7..912448e43c 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -23,7 +23,7 @@ module ActiveModel
# attr_reader :errors
#
# def validate!
- # errors.add(:name, "cannot be nil") if name.nil?
+ # errors.add(:name, :blank, message: "cannot be nil") if name.nil?
# end
#
# # The following methods are needed to be minimally implemented
@@ -58,8 +58,9 @@ module ActiveModel
include Enumerable
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
+ MESSAGE_OPTIONS = [:message]
- attr_reader :messages
+ attr_reader :messages, :details
# Pass in the instance of the object that is using the errors object.
#
@@ -71,10 +72,12 @@ module ActiveModel
def initialize(base)
@base = base
@messages = {}
+ @details = Hash.new { |details, attribute| details[attribute] = [] }
end
def initialize_dup(other) # :nodoc:
@messages = other.messages.dup
+ @details = other.details.dup
super
end
@@ -85,6 +88,7 @@ module ActiveModel
# person.errors.full_messages # => []
def clear
messages.clear
+ details.clear
end
# Returns +true+ if the error messages include an error for the given key
@@ -126,6 +130,7 @@ module ActiveModel
# person.errors.get(:name) # => nil
def delete(key)
messages.delete(key)
+ details.delete(key)
end
# When passed a symbol or a name of a method, returns an array of errors
@@ -149,12 +154,12 @@ 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.
#
- # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
@@ -167,9 +172,9 @@ module ActiveModel
# Returns the number of error messages.
#
- # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.size # => 1
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.size # => 2
def size
values.flatten.size
@@ -193,8 +198,8 @@ module ActiveModel
# Returns an array of error messages, with the attribute name included.
#
- # person.errors.add(:name, "can't be blank")
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :blank, message: "can't be blank")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.to_a # => ["name can't be blank", "name must be specified"]
def to_a
full_messages
@@ -202,9 +207,9 @@ module ActiveModel
# Returns the number of error messages.
#
- # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.count # => 1
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.count # => 2
def count
to_a.size
@@ -223,8 +228,8 @@ module ActiveModel
# Returns an xml formatted representation of the Errors hash.
#
- # person.errors.add(:name, "can't be blank")
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :blank, message: "can't be blank")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.to_xml
# # =>
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
@@ -261,17 +266,20 @@ 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 and used validator type to +details+ on +attribute+.
+ # More than one error can be added to the same +attribute+.
+ # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
# person.errors.add(:name)
# # => ["is invalid"]
- # person.errors.add(:name, 'must be implemented')
+ # person.errors.add(:name, :not_implemented, message: "must be implemented")
# # => ["is invalid", "must be implemented"]
#
# person.errors.messages
- # # => {:name=>["must be implemented", "is invalid"]}
+ # # => {:name=>["is invalid", "must be implemented"]}
+ #
+ # person.errors.details
+ # # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
#
# If +message+ is a symbol, it will be translated using the appropriate
# scope (see +generate_message+).
@@ -293,16 +301,22 @@ module ActiveModel
# +attribute+ should be set to <tt>:base</tt> if the error is not
# directly associated with a single attribute.
#
- # person.errors.add(:base, "either name or email must be present")
+ # person.errors.add(:base, :name_or_email_blank,
+ # message: "either name or email must be present")
# person.errors.messages
# # => {:base=>["either name or email must be present"]}
+ # person.errors.details
+ # # => {:base=>[{error: :name_or_email_blank}]}
def add(attribute, message = :invalid, options = {})
+ message = message.call if message.respond_to?(:call)
+ detail = normalize_detail(attribute, message, options)
message = normalize_message(attribute, message, options)
if exception = options[:strict]
exception = ActiveModel::StrictValidationFailed if exception == true
raise exception, full_message(attribute, message)
end
+ details[attribute.to_sym] << detail
self[attribute] << message
end
@@ -339,6 +353,7 @@ module ActiveModel
# person.errors.add :name, :blank
# person.errors.added? :name, :blank # => true
def added?(attribute, message = :invalid, options = {})
+ message = message.call if message.respond_to?(:call)
message = normalize_message(attribute, message, options)
self[attribute].include? message
end
@@ -388,8 +403,8 @@ module ActiveModel
# Translates an error message in its default scope
# (<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
+ # Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
+ # if it's not there, it's looked up in <tt>activemodel.errors.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
@@ -447,12 +462,14 @@ module ActiveModel
case message
when Symbol
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- when Proc
- message.call
else
message
end
end
+
+ def normalize_detail(attribute, message, options)
+ { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
+ end
end
# Raised when a validation cannot be corrected by end users and are considered
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index ada1f9a4f3..2bc3eeaa19 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
+require 'active_support/core_ext/module/remove_method'
module ActiveModel
class Name
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index ac5e79859b..ee160fb483 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -3,12 +3,12 @@ module ActiveModel
module Validations
class AcceptanceValidator < EachValidator # :nodoc:
def initialize(options)
- super({ allow_nil: true, accept: "1" }.merge!(options))
+ super({ allow_nil: true, accept: ["1", true] }.merge!(options))
setup!(options[:class])
end
def validate_each(record, attribute, value)
- unless value == options[:accept]
+ unless acceptable_option?(value)
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
end
end
@@ -20,6 +20,10 @@ module ActiveModel
klass.send(:attr_reader, *attr_readers)
klass.send(:attr_writer, *attr_writers)
end
+
+ def acceptable_option?(value)
+ Array(options[:accept]).include?(value)
+ end
end
module HelperMethods
diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb
new file mode 100644
index 0000000000..402caf21f7
--- /dev/null
+++ b/activemodel/test/cases/attribute_assignment_test.rb
@@ -0,0 +1,107 @@
+require "cases/helper"
+require "active_support/hash_with_indifferent_access"
+
+class AttributeAssignmentTest < ActiveModel::TestCase
+ class Model
+ include ActiveModel::AttributeAssignment
+
+ attr_accessor :name, :description
+
+ def initialize(attributes = {})
+ assign_attributes(attributes)
+ end
+
+ def broken_attribute=(value)
+ raise ErrorFromAttributeWriter
+ end
+
+ protected
+
+ attr_writer :metadata
+ end
+
+ class ErrorFromAttributeWriter < StandardError
+ end
+
+ class ProtectedParams < ActiveSupport::HashWithIndifferentAccess
+ def permit!
+ @permitted = true
+ end
+
+ def permitted?
+ @permitted ||= false
+ end
+
+ def dup
+ super.tap do |duplicate|
+ duplicate.instance_variable_set :@permitted, permitted?
+ end
+ end
+ end
+
+ test "simple assignment" do
+ model = Model.new
+
+ model.assign_attributes(name: "hello", description: "world")
+ assert_equal "hello", model.name
+ assert_equal "world", model.description
+ end
+
+ test "assign non-existing attribute" do
+ model = Model.new
+ error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do
+ model.assign_attributes(hz: 1)
+ end
+
+ assert_equal model, error.record
+ assert_equal "hz", error.attribute
+ end
+
+ test "assign private attribute" do
+ model = Model.new
+ assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do
+ model.assign_attributes(metadata: { a: 1 })
+ end
+ end
+
+ test "does not swallow errors raised in an attribute writer" do
+ assert_raises(ErrorFromAttributeWriter) do
+ Model.new(broken_attribute: 1)
+ end
+ end
+
+ test "an ArgumentError is raised if a non-hash-like obejct is passed" do
+ assert_raises(ArgumentError) do
+ Model.new(1)
+ end
+ end
+
+ test "forbidden attributes cannot be used for mass assignment" do
+ params = ProtectedParams.new(name: "Guille", description: "m")
+
+ assert_raises(ActiveModel::ForbiddenAttributesError) do
+ Model.new(params)
+ end
+ end
+
+ test "permitted attributes can be used for mass assignment" do
+ params = ProtectedParams.new(name: "Guille", description: "desc")
+ params.permit!
+ model = Model.new(params)
+
+ assert_equal "Guille", model.name
+ assert_equal "desc", model.description
+ end
+
+ test "regular hash should still be used for mass assignment" do
+ model = Model.new(name: "Guille", description: "m")
+
+ assert_equal "Guille", model.name
+ assert_equal "m", model.description
+ end
+
+ test "assigning no attributes should not raise, even if the hash is un-permitted" do
+ model = Model.new
+ assert_nil model.assign_attributes(ProtectedParams.new({}))
+ end
+end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index efedd9055f..9cfba9812b 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -323,4 +323,46 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' })
person.errors.add_on_blank :name, message: 'custom'
end
+
+ test "details returns added error detail" do
+ person = Person.new
+ person.errors.add(:name, :invalid)
+ assert_equal({ name: [{ error: :invalid }] }, person.errors.details)
+ end
+
+ test "details returns added error detail with custom option" do
+ person = Person.new
+ person.errors.add(:name, :greater_than, count: 5)
+ assert_equal({ name: [{ error: :greater_than, count: 5 }] }, person.errors.details)
+ end
+
+ test "details do not include message option" do
+ person = Person.new
+ person.errors.add(:name, :invalid, message: "is bad")
+ assert_equal({ name: [{ error: :invalid }] }, person.errors.details)
+ end
+
+ test "dup duplicates details" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ errors_dup = errors.dup
+ errors_dup.add(:name, :taken)
+ assert_not_same errors_dup.details, errors.details
+ end
+
+ test "delete removes details on given attribute" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ errors.delete(:name)
+ assert_empty errors.details[:name]
+ end
+
+ test "clear removes details" do
+ person = Person.new
+ person.errors.add(:name, :invalid)
+
+ assert_equal 1, person.errors.details.count
+ person.errors.clear
+ assert person.errors.details.empty?
+ end
end
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index e78aa1adaf..b7872ea6bf 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -65,4 +65,10 @@ class AcceptanceValidationTest < ActiveModel::TestCase
ensure
Person.clear_validators!
end
+
+ def test_validates_acceptance_of_true
+ Topic.validates_acceptance_of(:terms_of_service)
+
+ assert Topic.new(terms_of_service: true).valid?
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 8875b7ae25..22b1c8e95b 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,80 @@
+* Integer types will no longer raise a `RangeError` when assigning an
+ attribute, but will instead raise when going to the database.
+
+ Fixes several vague issues which were never reported directly. See the
+ commit message from the commit which added this line for some examples.
+
+ *Sean Griffin*
+
+* Values which would error while being sent to the database (such as an
+ ASCII-8BIT string with invalid UTF-8 bytes on Sqlite3), no longer error on
+ assignment. They will still error when sent to the database, but you are
+ given the ability to re-assign it to a valid value.
+
+ Fixes #18580.
+
+ *Sean Griffin*
+
+* Don't remove join dependencies in `Relation#exists?`
+
+ Fixes #18632.
+
+ *Sean Griffin*
+
+* Invalid values assigned to a JSON column are assumed to be `nil`.
+
+ Fixes #18629.
+
+ *Sean Griffin*
+
+* Add `ActiveRecord::Base#accessed_fields`, which can be used to quickly
+ discover which fields were read from a model when you are looking to only
+ select the data you need from the database.
+
+ *Sean Griffin*
+
+* Introduce the `:if_exists` option for `drop_table`.
+
+ Example:
+
+ drop_table(:posts, if_exists: true)
+
+ That would execute:
+
+ DROP TABLE IF EXISTS posts
+
+ If the table doesn't exist, `if_exists: false` (the default) raises an
+ exception whereas `if_exists: true` does nothing.
+
+ *Cody Cutrer*, *Stefan Kanev*, *Ryuta Kamizono*
+
+* Don't run SQL if attribute value is not changed for update_attribute method.
+
+ *Prathamesh Sonpatki*
+
+* `time` columns can now get affected by `time_zone_aware_attributes`. If you have
+ set `config.time_zone` to a value other than `'UTC'`, they will be treated
+ as in that time zone by default in Rails 5.1. If this is not the desired
+ behavior, you can set
+
+ ActiveRecord::Base.time_zone_aware_types = [:datetime]
+
+ A deprecation warning will be emitted if you have a `:time` column, and have
+ not explicitly opted out.
+
+ Fixes #3145
+
+ *Sean Griffin*
+
+* Tests now run after_commit callbacks. You no longer have to declare
+ `uses_transaction ‘test name’` to test the results of an after_commit.
+
+ after_commit callbacks run after committing a transaction whose parent
+ is not `joinable?`: un-nested transactions, transactions within test cases,
+ and transactions in `console --sandbox`.
+
+ *arthurnn*, *Ravil Bayramgalin*, *Matthew Draper*
+
* `nil` as a value for a binary column in a query no longer logs as
"<NULL binary data>", and instead logs as just "nil".
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index 7e3460365b..bae40604b1 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -16,7 +16,7 @@ To run a set of tests:
You can also run tests that depend upon a specific database backend. For
example:
- $ bundle exec rake test_sqlite3
+ $ bundle exec rake test:sqlite3
Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
@@ -24,6 +24,10 @@ Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
$ bundle exec rake test:mysql2
$ bundle exec rake test:postgresql
$ bundle exec rake test:sqlite3
+
+Using the SQLite3 adapter with an in-memory database is the fastest way
+to run the tests:
+
$ bundle exec rake test:sqlite3_mem
There should be tests available for each database backend listed in the {Config
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 88536eaac0..73bb059495 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -51,7 +51,7 @@ module ActiveRecord
end
def changed_in_place_from?(old_value)
- type.changed_in_place?(old_value, value)
+ has_been_read? && type.changed_in_place?(old_value, value)
end
def with_value_from_user(value)
@@ -74,6 +74,14 @@ module ActiveRecord
true
end
+ def came_from_user?
+ false
+ end
+
+ def has_been_read?
+ defined?(@value)
+ end
+
def ==(other)
self.class == other.class &&
name == other.name &&
@@ -99,6 +107,10 @@ module ActiveRecord
def type_cast(value)
type.type_cast_from_user(value)
end
+
+ def came_from_user?
+ true
+ end
end
class WithCastValue < Attribute # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index bf64830417..e368fdfff9 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -3,63 +3,32 @@ require 'active_model/forbidden_attributes_protection'
module ActiveRecord
module AttributeAssignment
extend ActiveSupport::Concern
- include ActiveModel::ForbiddenAttributesProtection
-
- # Allows you to set all the attributes by passing in a hash of attributes with
- # keys matching the attribute names (which again matches the column names).
- #
- # If the passed hash responds to <tt>permitted?</tt> method and the return value
- # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
- # exception is raised.
- #
- # cat = Cat.new(name: "Gorby", status: "yawning")
- # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
- # cat.assign_attributes(status: "sleeping")
- # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
- #
- # New attributes will be persisted in the database when the object is saved.
- #
- # Aliased to <tt>attributes=</tt>.
- def assign_attributes(new_attributes)
- if !new_attributes.respond_to?(:stringify_keys)
- raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
- end
- return if new_attributes.blank?
-
- attributes = new_attributes.stringify_keys
- multi_parameter_attributes = []
- nested_parameter_attributes = []
+ include ActiveModel::AttributeAssignment
- attributes = sanitize_for_mass_assignment(attributes)
+ def _assign_attributes(attributes) # :nodoc:
+ multi_parameter_attributes = {}
+ nested_parameter_attributes = {}
attributes.each do |k, v|
if k.include?("(")
- multi_parameter_attributes << [ k, v ]
+ multi_parameter_attributes[k] = attributes.delete(k)
elsif v.is_a?(Hash)
- nested_parameter_attributes << [ k, v ]
- else
- _assign_attribute(k, v)
+ nested_parameter_attributes[k] = attributes.delete(k)
end
end
+ super(attributes)
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- alias attributes= assign_attributes
+ # Alias for `assign_attributes`. See +ActiveModel::AttributeAssignment+
+ def attributes=(attributes)
+ assign_attributes(attributes)
+ end
private
- def _assign_attribute(k, v)
- public_send("#{k}=", v)
- rescue NoMethodError
- if respond_to?("#{k}=")
- raise
- else
- raise UnknownAttributeError.new(self, k)
- end
- end
-
# Assign any deferred nested attributes after the base attributes have been set.
def assign_nested_parameter_attributes(pairs)
pairs.each { |k, v| _assign_attribute(k, v) }
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 8f165fb1dc..b2db0ceae7 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -369,6 +369,39 @@ module ActiveRecord
write_attribute(attr_name, value)
end
+ # Returns the name of all database fields which have been read from this
+ # model. This can be useful in devleopment mode to determine which fields
+ # need to be selected. For performance critical pages, selecting only the
+ # required fields can be an easy performance win (assuming you aren't using
+ # all of the fields on the model).
+ #
+ # For example:
+ #
+ # class PostsController < ActionController::Base
+ # after_action :print_accessed_fields, only: :index
+ #
+ # def index
+ # @posts = Post.all
+ # end
+ #
+ # private
+ #
+ # def print_accessed_fields
+ # p @posts.first.accessed_fields
+ # end
+ # end
+ #
+ # Which allows you to quickly change your code to:
+ #
+ # class PostsController < ActionController::Base
+ # def index
+ # @posts = Post.select(:id, :title, :author_id, :updated_at)
+ # end
+ # end
+ def accessed_fields
+ @attributes.accessed
+ end
+
protected
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index fd61febd57..56c1898551 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -28,6 +28,7 @@ module ActiveRecord
included do
attribute_method_suffix "_before_type_cast"
+ attribute_method_suffix "_came_from_user?"
end
# Returns the value of the attribute identified by +attr_name+ before
@@ -66,6 +67,10 @@ module ActiveRecord
def attribute_before_type_cast(attribute_name)
read_attribute_before_type_cast(attribute_name)
end
+
+ def attribute_came_from_user?(attribute_name)
+ @attributes[attribute_name].came_from_user?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index ce7f575150..06d87ee01e 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -108,7 +108,7 @@ module ActiveRecord
end
def save_changed_attribute(attr, old_value)
- if attribute_changed?(attr)
+ if attribute_changed_by_setter?(attr)
clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
else
set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
@@ -165,7 +165,7 @@ module ActiveRecord
end
def store_original_raw_attribute(attr_name)
- original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
end
def store_original_raw_attributes
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 777f7ab4d7..98671178cb 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -13,7 +13,7 @@ module ActiveRecord
value.map { |v| type_cast_from_user(v) }
elsif value.respond_to?(:in_time_zone)
begin
- value.in_time_zone || super
+ user_input_in_time_zone(value) || super
rescue ArgumentError
nil
end
@@ -39,6 +39,9 @@ module ActiveRecord
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
self.skip_time_zone_conversion_for_attributes = []
+
+ class_attribute :time_zone_aware_types, instance_writer: false
+ self.time_zone_aware_types = [:datetime, :not_explicitly_configured]
end
module ClassMethods
@@ -59,9 +62,31 @@ module ActiveRecord
end
def create_time_zone_conversion_attribute?(name, cast_type)
- time_zone_aware_attributes &&
- !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
- (:datetime == cast_type.type)
+ enabled_for_column = time_zone_aware_attributes &&
+ !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
+ result = enabled_for_column &&
+ time_zone_aware_types.include?(cast_type.type)
+
+ if enabled_for_column &&
+ !result &&
+ cast_type.type == :time &&
+ time_zone_aware_types.include?(:not_explicitly_configured)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE)
+ Time columns will become time zone aware in Rails 5.1. This
+ still cause `String`s to be parsed as if they were in `Time.zone`,
+ and `Time`s to be converted to `Time.zone`.
+
+ To keep the old behavior, you must add the following to your initializer:
+
+ config.active_record.time_zone_aware_types = [:datetime]
+
+ To silence this deprecation warning, add the following:
+
+ config.active_record.time_zone_aware_types << :time
+ MESSAGE
+ end
+
+ result
end
end
end
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 66fcaf6945..fdce68ce45 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -64,6 +64,10 @@ module ActiveRecord
end
end
+ def accessed
+ attributes.select { |_, attr| attr.has_been_read? }.keys
+ end
+
protected
attr_reader :attributes
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index b263a89d79..faf5d632ec 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -14,7 +14,7 @@ module ActiveRecord
end
module ClassMethods # :nodoc:
- # Defines or overrides a attribute on this model. This allows customization of
+ # Defines or overrides an attribute on this model. This allows customization of
# Active Record's type casting behavior, as well as adding support for user defined
# types.
#
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 6b5081b7a9..d99dc9a5db 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -454,7 +454,8 @@ module ActiveRecord
end
c
rescue
- disconnect!
+ remove c
+ c.disconnect!
raise
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 6e631ed9f7..787d07c4c2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -289,7 +289,13 @@ module ActiveRecord
[columns[name], value]
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
- value_list = prepare_binds_for_database(binds).map { |_, value| quote(value) }
+ value_list = prepare_binds_for_database(binds).map do |_, value|
+ begin
+ quote(value)
+ rescue TypeError
+ quote(YAML.dump(value))
+ end
+ end
execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 7c1a779577..c18caa2a2f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -125,8 +125,7 @@ module ActiveRecord
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value}'"
- else
- "'#{quote_string(YAML.dump(value))}'"
+ else raise TypeError, "can't quote #{value.class.name}"
end
end
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 0f44c332ae..f905669a24 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -380,7 +380,7 @@ module ActiveRecord
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
- execute "DROP TABLE #{quote_table_name(table_name)}"
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
end
# Adds a new column to the named table.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 7535e9147a..7f738e89c9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -111,7 +111,6 @@ module ActiveRecord
def rollback
connection.rollback_to_savepoint(savepoint_name)
super
- rollback_records
end
def commit
@@ -136,13 +135,11 @@ module ActiveRecord
def rollback
connection.rollback_db_transaction
super
- rollback_records
end
def commit
connection.commit_db_transaction
super
- commit_records
end
end
@@ -159,18 +156,28 @@ module ActiveRecord
else
SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
end
+
@stack.push(transaction)
transaction
end
def commit_transaction
- transaction = @stack.pop
- transaction.commit
- transaction.records.each { |r| current_transaction.add_record(r) }
+ inner_transaction = @stack.pop
+ inner_transaction.commit
+
+ if current_transaction.joinable?
+ inner_transaction.records.each do |r|
+ current_transaction.add_record(r)
+ end
+ else
+ inner_transaction.commit_records
+ end
end
- def rollback_transaction
- @stack.pop.rollback
+ def rollback_transaction(transaction = nil)
+ transaction ||= @stack.pop
+ transaction.rollback
+ transaction.rollback_records
end
def within_new_transaction(options = {})
@@ -187,7 +194,7 @@ module ActiveRecord
begin
commit_transaction
rescue Exception
- transaction.rollback unless transaction.state.completed?
+ rollback_transaction(transaction) unless transaction.state.completed?
raise
end
end
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 e9a3c26c32..b61b717a61 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -502,7 +502,7 @@ module ActiveRecord
end
def drop_table(table_name, options = {})
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
def rename_index(table_name, old_name, new_name)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
index c203e6c604..e45a2f59d9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -18,7 +18,7 @@ module ActiveRecord
end
attr_reader :subtype, :delimiter
- delegate :type, to: :subtype
+ delegate :type, :user_input_in_time_zone, to: :subtype
def initialize(subtype, delimiter = ',')
@subtype = subtype
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
index e12ddd9901..7dadc09a44 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
@@ -11,7 +11,7 @@ module ActiveRecord
def type_cast_from_database(value)
if value.is_a?(::String)
- ::ActiveSupport::JSON.decode(value)
+ ::ActiveSupport::JSON.decode(value) rescue nil
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index a90adcf4aa..afd7a17c03 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -112,7 +112,7 @@ module ActiveRecord
end
def drop_table(table_name, options = {})
- execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
# Returns true if schema exists.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 03dfd29a0a..52dce6291a 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -53,7 +53,7 @@ module ActiveRecord
class SQLite3String < Type::String # :nodoc:
def type_cast_for_database(value)
if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT
- value.encode(Encoding::UTF_8)
+ value.encode(Encoding::UTF_8, undef: :replace)
else
super
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index a5eb18246f..4705f129f2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -456,26 +456,23 @@ module ActiveRecord
# Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record`
# when pp is required.
def pretty_print(pp)
- if self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
- pp.text inspect
- else
- pp.object_address_group(self) do
- if defined?(@attributes) && @attributes
- column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
- pp.seplist(column_names, proc { pp.text ',' }) do |column_name|
- column_value = read_attribute(column_name)
- pp.breakable ' '
- pp.group(1) do
- pp.text column_name
- pp.text ':'
- pp.breakable
- pp.pp column_value
- end
- end
- else
+ return super if custom_inspect_method_defined?
+ pp.object_address_group(self) do
+ if defined?(@attributes) && @attributes
+ column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
+ pp.seplist(column_names, proc { pp.text ',' }) do |column_name|
+ column_value = read_attribute(column_name)
pp.breakable ' '
- pp.text 'not initialized'
+ pp.group(1) do
+ pp.text column_name
+ pp.text ':'
+ pp.breakable
+ pp.pp column_value
+ end
end
+ else
+ pp.breakable ' '
+ pp.text 'not initialized'
end
end
end
@@ -564,5 +561,9 @@ module ActiveRecord
@attributes = @attributes.dup
end
end
+
+ def custom_inspect_method_defined?
+ self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
+ end
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index fc28ab585f..d710d96a9a 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -178,18 +178,10 @@ module ActiveRecord
class DangerousAttributeError < ActiveRecordError
end
- # Raised when unknown attributes are supplied via mass assignment.
- class UnknownAttributeError < NoMethodError
-
- attr_reader :record, :attribute
-
- def initialize(record, attribute)
- @record = record
- @attribute = attribute.to_s
- super("unknown attribute '#{attribute}' for #{@record.class}.")
- end
-
- end
+ UnknownAttributeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new( # :nodoc:
+ 'ActiveRecord::UnknownAttributeError',
+ 'ActiveModel::AttributeAssignment::UnknownAttributeError'
+ )
# Raised when an error occurred while doing a mass assignment to an attribute through the
# +attributes=+ method. The exception has an +attribute+ property that is the name of the
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index aeb1a4ddc6..6f2b65c137 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -125,12 +125,8 @@ module ActiveRecord
relation = super
if locking_enabled?
- column_name = self.class.locking_column
- column = self.class.columns_hash[column_name]
- substitute = self.class.connection.substitute_at(column)
-
- relation = relation.where(self.class.arel_table[column_name].eq(substitute))
- relation.bind_values << [column, self[column_name].to_i]
+ locking_column = self.class.locking_column
+ relation = relation.where(locking_column => _read_attribute(locking_column))
end
relation
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index cf6673db2e..22112fe8ff 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -245,8 +245,8 @@ module ActiveRecord
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
- send("#{name}=", value)
- save(validate: false)
+ public_send("#{name}=", value)
+ save(validate: false) if changed?
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -352,7 +352,7 @@ module ActiveRecord
# method toggles directly the underlying value without calling any setter.
# Returns +self+.
def toggle(attribute)
- self[attribute] = !send("#{attribute}?")
+ self[attribute] = !public_send("#{attribute}?")
self
end
@@ -495,15 +495,7 @@ module ActiveRecord
end
def relation_for_destroy
- pk = self.class.primary_key
- column = self.class.columns_hash[pk]
- substitute = self.class.connection.substitute_at(column)
-
- relation = self.class.unscoped.where(
- self.class.arel_table[pk].eq(substitute))
-
- relation.bind_values = [[column, id]]
- relation
+ self.class.unscoped.where(self.class.primary_key => id)
end
def create_or_update(*args)
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index dd78814c6a..9c4db8a05e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -33,7 +33,6 @@ module ActiveRecord
# This method is a hot spot, so for now, use Hash[] to dup the hash.
# https://bugs.ruby-lang.org/issues/7166
@values = Hash[@values]
- @values[:bind] = @values[:bind].dup if @values.key? :bind
reset
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 088bc203b7..c83abfba06 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -307,7 +307,7 @@ module ActiveRecord
relation = relation.where(conditions)
else
unless conditions == :none
- relation = where(primary_key => conditions)
+ relation = relation.where(primary_key => conditions)
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 567efce8ae..2860a30f99 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -28,6 +28,27 @@ module ActiveRecord
expand_from_hash(attributes)
end
+ def create_binds(attributes)
+ result = attributes.dup
+ binds = []
+
+ attributes.each do |column_name, value|
+ case value
+ when String, Integer, ActiveRecord::StatementCache::Substitute
+ result[column_name] = Arel::Nodes::BindParam.new
+ binds.push([table.column(column_name), value])
+ when Hash
+ attrs, bvs = associated_predicate_builder(column_name).create_binds(value)
+ result[column_name] = attrs
+ binds += bvs
+ when Relation
+ binds += value.arel.bind_values + value.bind_values
+ end
+ end
+
+ [result, binds]
+ end
+
def expand(column, value)
# Find the foreign key when using queries such as:
# Post.where(author: author)
@@ -80,8 +101,7 @@ module ActiveRecord
attributes.flat_map do |key, value|
if value.is_a?(Hash)
- builder = self.class.new(table.associated_table(key))
- builder.expand_from_hash(value)
+ associated_predicate_builder(key).expand_from_hash(value)
else
expand(key, value)
end
@@ -90,6 +110,10 @@ module ActiveRecord
private
+ def associated_predicate_builder(association_name)
+ self.class.new(table.associated_table(association_name))
+ end
+
def convert_dot_notation_to_hash(attributes)
dot_notation = attributes.keys.select { |s| s.include?(".") }
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index d6e6cb4d05..c34e4bfb9b 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -909,7 +909,7 @@ module ActiveRecord
end
end
- bind_values.reject! { |col,_| col.name == target_value }
+ self.bind_values = bind_values.reject { |col,_| col.name == target_value }
end
def custom_join_ast(table, joins)
@@ -945,11 +945,10 @@ module ActiveRecord
when Hash
opts = predicate_builder.resolve_column_aliases(opts)
- tmp_opts, bind_values = create_binds(opts)
+ tmp_opts, bind_values = predicate_builder.create_binds(opts)
self.bind_values += bind_values
attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
- add_relations_to_bind_values(attributes)
predicate_builder.build_from_hash(attributes)
else
@@ -957,40 +956,6 @@ module ActiveRecord
end
end
- def create_binds(opts)
- bindable, non_binds = opts.partition do |column, value|
- case value
- when String, Integer, ActiveRecord::StatementCache::Substitute
- @klass.columns_hash.include? column.to_s
- else
- false
- end
- end
-
- association_binds, non_binds = non_binds.partition do |column, value|
- value.is_a?(Hash) && association_for_table(column)
- end
-
- new_opts = {}
- binds = []
-
- bindable.each do |(column,value)|
- binds.push [@klass.columns_hash[column.to_s], value]
- new_opts[column] = connection.substitute_at(column)
- end
-
- association_binds.each do |(column, value)|
- association_relation = association_for_table(column).klass.send(:relation)
- association_new_opts, association_bind = association_relation.send(:create_binds, value)
- new_opts[column] = association_new_opts
- binds += association_bind
- end
-
- non_binds.each { |column,value| new_opts[column] = value }
-
- [new_opts, binds]
- end
-
def association_for_table(table_name)
table_name = table_name.to_s
@klass._reflect_on_association(table_name) ||
@@ -1153,17 +1118,5 @@ module ActiveRecord
raise ArgumentError, "The method .#{method_name}() must contain arguments."
end
end
-
- def add_relations_to_bind_values(attributes)
- if attributes.is_a?(Hash)
- attributes.each_value do |value|
- if value.is_a?(ActiveRecord::Relation)
- self.bind_values += value.arel.bind_values + value.bind_values
- else
- add_relations_to_bind_values(value)
- end
- end
- end
- end
end
end
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index be3c3bc847..07031b6371 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -5,7 +5,7 @@ module ActiveRecord
module ClassMethods
# Example using has_secure_token
#
- # # Schema: User(toke:string, auth_token:string)
+ # # Schema: User(token:string, auth_token:string)
# class User < ActiveRecord::Base
# has_secure_token
# has_secure_token :auth_token
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index 11e33e8dfe..31a40adb67 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -22,6 +22,12 @@ module ActiveRecord
arel_table[column_name]
end
+ def column(column_name)
+ if klass
+ klass.columns_hash[column_name.to_s]
+ end
+ end
+
def associated_with?(association_name)
klass && klass._reflect_on_association(association_name)
end
diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb
index fc260a081a..90ca9f88da 100644
--- a/activerecord/lib/active_record/type/integer.rb
+++ b/activerecord/lib/active_record/type/integer.rb
@@ -3,6 +3,10 @@ module ActiveRecord
class Integer < Value # :nodoc:
include Numeric
+ # Column storage size in bytes.
+ # 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc.
+ DEFAULT_LIMIT = 4
+
def initialize(*)
super
@range = min_value...max_value
@@ -12,13 +16,19 @@ module ActiveRecord
:integer
end
- alias type_cast_for_database type_cast
-
def type_cast_from_database(value)
return if value.nil?
value.to_i
end
+ def type_cast_for_database(value)
+ result = type_cast(value)
+ if result
+ ensure_in_range(result)
+ end
+ result
+ end
+
protected
attr_reader :range
@@ -30,20 +40,18 @@ module ActiveRecord
when true then 1
when false then 0
else
- result = value.to_i rescue nil
- ensure_in_range(result) if result
- result
+ value.to_i rescue nil
end
end
def ensure_in_range(value)
unless range.cover?(value)
- raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || 4}"
+ raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || DEFAULT_LIMIT}"
end
end
def max_value
- limit = self.limit || 4
+ limit = self.limit || DEFAULT_LIMIT
1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign
end
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 41f7d97f0c..cab1c7bf1e 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -7,6 +7,19 @@ module ActiveRecord
:time
end
+ def user_input_in_time_zone(value)
+ return unless value.present?
+
+ case value
+ when ::String
+ value = "2000-01-01 #{value}"
+ when ::Time
+ value = value.change(year: 2000, day: 1, month: 1)
+ end
+
+ super(value)
+ end
+
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/type/time_value.rb b/activerecord/lib/active_record/type/time_value.rb
index d611d72dd4..8d9ac25643 100644
--- a/activerecord/lib/active_record/type/time_value.rb
+++ b/activerecord/lib/active_record/type/time_value.rb
@@ -9,6 +9,10 @@ module ActiveRecord
"'#{value.to_s(:db)}'"
end
+ def user_input_in_time_zone(value)
+ value.in_time_zone
+ end
+
private
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 60ae47db3d..859b51ca90 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -3,8 +3,7 @@ module ActiveRecord
class Value # :nodoc:
attr_reader :precision, :scale, :limit
- # Valid options are +precision+, +scale+, and +limit+. They are only
- # used when dumping schema.
+ # Valid options are +precision+, +scale+, and +limit+.
def initialize(options = {})
options.assert_valid_keys(:precision, :scale, :limit)
@precision = options[:precision]
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 9c49599d34..06b0cb5515 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -16,7 +16,7 @@ module ActiveRecord
def test_initializes_schema_migrations_for_encoding_utf8mb4
smtn = ActiveRecord::Migrator.schema_migrations_table_name
- connection.drop_table(smtn) if connection.table_exists?(smtn)
+ connection.drop_table smtn, if_exists: true
config = connection.instance_variable_get(:@config)
original_encoding = config[:encoding]
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 77055f5b7a..331f33455e 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -112,8 +112,8 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def test_schema_dump_with_shorthand
output = dump_table_schema "pg_arrays"
- assert_match %r[t.string\s+"tags",\s+array: true], output
- assert_match %r[t.integer\s+"ratings",\s+array: true], output
+ assert_match %r[t\.string\s+"tags",\s+array: true], output
+ assert_match %r[t\.integer\s+"ratings",\s+array: true], output
end
def test_select_with_strings
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index 5a8083f7a7..cfbe842948 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -72,7 +72,7 @@ if ActiveRecord::Base.connection.supports_extensions?
def test_schema_dump_with_shorthand
output = dump_table_schema("citexts")
- assert_match %r[t.citext "cival"], output
+ assert_match %r[t\.citext "cival"], output
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
index a370a5adc6..e7e5d8e32a 100644
--- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -39,6 +39,6 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase
def test_schema_dump_with_shorthand
output = dump_table_schema("tsvectors")
- assert_match %r{t.tsvector "text_vector"}, output
+ assert_match %r{t\.tsvector "text_vector"}, output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index a0aa10630c..2ca123409a 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -317,7 +317,7 @@ if ActiveRecord::Base.connection.supports_extensions?
def test_schema_dump_with_shorthand
output = dump_table_schema("hstores")
- assert_match %r[t.hstore "tags",\s+default: {}], output
+ assert_match %r[t\.hstore "tags",\s+default: {}], output
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 7be7e00463..f5ae872483 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -66,7 +66,7 @@ module PostgresqlJSONSharedTestCases
def test_schema_dumping
output = dump_table_schema("json_data_type")
- assert_match(/t.#{column_type.to_s}\s+"payload",\s+default: {}/, output)
+ assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output)
end
def test_cast_value_on_write
@@ -179,6 +179,14 @@ module PostgresqlJSONSharedTestCases
assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload)
assert_not json.changed?
end
+
+ def test_assigning_invalid_json
+ json = JsonDataType.new
+
+ json.payload = 'foo'
+
+ assert_nil json.payload
+ end
end
class PostgresqlJSONTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 771a825840..c5371348ea 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -48,6 +48,6 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
def test_schema_dump_with_shorthand
output = dump_table_schema("ltrees")
- assert_match %r[t.ltree "path"], output
+ assert_match %r[t\.ltree "path"], output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index f3a24eee85..78afd49b0b 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -56,7 +56,7 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
def test_schema_dumping
output = dump_table_schema("postgresql_moneys")
assert_match %r{t\.money\s+"wealth",\s+scale: 2$}, output
- assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150.55$}, output
+ assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150\.55$}, output
end
def test_create_and_update_money
diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb
index daa590f369..741876d3ca 100644
--- a/activerecord/test/cases/adapters/postgresql/network_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -85,8 +85,8 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase
def test_schema_dump_with_shorthand
output = dump_table_schema("postgresql_network_addresses")
- assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output
- assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output
- assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output
+ assert_match %r{t\.inet\s+"inet_address",\s+default: "192\.168\.1\.1"}, output
+ assert_match %r{t\.cidr\s+"cidr_address",\s+default: "192\.168\.1\.0/24"}, output
+ assert_match %r{t\.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index e99f1e2867..642b63357f 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -454,7 +454,7 @@ class SchemaForeignKeyTest < ActiveRecord::TestCase
end
@connection.add_foreign_key "wagons", "my_schema.trains", column: "train_id"
output = dump_table_schema "wagons"
- assert_match %r{\s+add_foreign_key "wagons", "my_schema.trains", column: "train_id"$}, output
+ assert_match %r{\s+add_foreign_key "wagons", "my_schema\.trains", column: "train_id"$}, output
ensure
@connection.execute "DROP TABLE IF EXISTS wagons"
@connection.execute "DROP TABLE IF EXISTS my_schema.trains"
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index c88259d274..4506e874bc 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -18,8 +18,8 @@ class PostgresqlTypeLookupTest < ActiveRecord::TestCase
bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]")
big_array = [123456789123456789]
- assert_raises(RangeError) { int_array.type_cast_from_user(big_array) }
- assert_equal big_array, bigint_array.type_cast_from_user(big_array)
+ assert_raises(RangeError) { int_array.type_cast_for_database(big_array) }
+ assert_equal "{123456789123456789}", bigint_array.type_cast_for_database(big_array)
end
test "range types correctly respect registration of subtypes" do
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 7d2fae69d5..d328f17a3f 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -114,7 +114,7 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
def test_schema_dump_with_shorthand
output = dump_table_schema "uuid_data_type"
- assert_match %r{t.uuid "guid"}, output
+ assert_match %r{t\.uuid "guid"}, output
end
def test_uniqueness_validation_ignores_uuid
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index 5aba118518..ac8464a65d 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -50,6 +50,6 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
def test_schema_dump_with_shorthand
output = dump_table_schema("xml_data_type")
- assert_match %r{t.xml "payload"}, output
+ assert_match %r{t\.xml "payload"}, output
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 21a45042fa..3bd7506bb5 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1307,7 +1307,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised { topic.destroy }
end
- uses_transaction :test_dependence_with_transaction_support_on_failure
def test_dependence_with_transaction_support_on_failure
firm = companies(:first_firm)
clients = firm.clients
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 9b6757e256..4df75adeb4 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -276,7 +276,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_create_with_inexistent_foreign_key_failing
firm = Firm.create(name: 'GlobalMegaCorp')
- assert_raises(ActiveRecord::UnknownAttributeError) do
+ assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do
firm.create_account_with_inexistent_foreign_key
end
end
diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index 321fb6c8dd..a6123ac432 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -18,8 +18,8 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
end
teardown do
- @connection.drop_table 'parents' if @connection.table_exists? 'parents'
- @connection.drop_table 'children' if @connection.table_exists? 'children'
+ @connection.drop_table 'parents', if_exists: true
+ @connection.drop_table 'children', if_exists: true
end
test "belongs_to associations are not required by default" do
diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb
index 53bd58e22e..9ad02ffae8 100644
--- a/activerecord/test/cases/attribute_decorators_test.rb
+++ b/activerecord/test/cases/attribute_decorators_test.rb
@@ -28,7 +28,7 @@ module ActiveRecord
teardown do
return unless @connection
- @connection.drop_table 'attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model'
+ @connection.drop_table 'attribute_decorators_model', if_exists: true
Model.attribute_type_decorations.clear
Model.reset_column_information
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 01ee1234a2..243c90e945 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -657,7 +657,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_attribute_in_current_time_zone
+ def test_setting_time_zone_aware_datetime_in_current_time_zone
utc_time = Time.utc(2008, 1, 1)
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
@@ -676,6 +676,47 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_setting_time_zone_aware_time_in_current_time_zone
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+ time_string = "10:00:00"
+ expected_time = Time.zone.parse("2000-01-01 #{time_string}")
+
+ record.bonus_time = time_string
+ assert_equal expected_time, record.bonus_time
+ assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone
+
+ record.bonus_time = ''
+ assert_nil record.bonus_time
+ end
+ end
+
+ def test_setting_time_zone_aware_time_with_dst
+ in_time_zone "Pacific Time (US & Canada)" do
+ current_time = Time.zone.local(2014, 06, 15, 10)
+ record = @target.new(bonus_time: current_time)
+ time_before_save = record.bonus_time
+
+ record.save
+ record.reload
+
+ assert_equal time_before_save, record.bonus_time
+ assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone
+ end
+ end
+
+ def test_removing_time_zone_aware_types
+ with_time_zone_aware_types(:datetime) do
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new(bonus_time: "10:00:00")
+ expected_time = Time.utc(2000, 01, 01, 10)
+
+ assert_equal expected_time, record.bonus_time
+ assert record.bonus_time.utc?
+ end
+ end
+ end
+
def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable
Topic.skip_time_zone_conversion_for_attributes = [:field_a]
Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b]
@@ -717,12 +758,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_bulk_update_respects_access_control
privatize("title=(value)")
- assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") }
- assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
+ assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new(:title => "Rants about pants") }
+ assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
end
def test_bulk_update_raise_unknown_attribute_error
- error = assert_raises(ActiveRecord::UnknownAttributeError) {
+ error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) {
Topic.new(hello: "world")
}
assert_instance_of Topic, error.record
@@ -888,6 +929,24 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_not_equal ['id'], @target.column_names
end
+ def test_came_from_user
+ model = @target.first
+
+ assert_not model.id_came_from_user?
+ model.id = "omg"
+ assert model.id_came_from_user?
+ end
+
+ def test_accessed_fields
+ model = @target.first
+
+ assert_equal [], model.accessed_fields
+
+ model.title
+
+ assert_equal ["title"], model.accessed_fields
+ end
+
private
def new_topic_like_ar_class(&block)
@@ -900,6 +959,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
klass
end
+ def with_time_zone_aware_types(*types)
+ old_types = ActiveRecord::Base.time_zone_aware_types
+ ActiveRecord::Base.time_zone_aware_types = types
+ yield
+ ensure
+ ActiveRecord::Base.time_zone_aware_types = old_types
+ end
+
def cached_columns
Topic.columns.map(&:name)
end
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
index ba53612d30..8025c7c4d2 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -186,5 +186,16 @@ module ActiveRecord
attributes.freeze
assert_equal({ foo: "1" }, attributes.to_hash)
end
+
+ test "#accessed_attributes returns only attributes which have been read" do
+ builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new)
+ attributes = builder.build_from_database(foo: "1", bar: "2")
+
+ assert_equal [], attributes.accessed
+
+ attributes.fetch_value(:foo)
+
+ assert_equal [:foo], attributes.accessed
+ end
end
end
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 39a976fcc8..eac73e11e8 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -169,5 +169,24 @@ module ActiveRecord
second = Attribute.from_user(:foo, 1, Type::Integer.new)
assert_not_equal first, second
end
+
+ test "an attribute has not been read by default" do
+ attribute = Attribute.from_database(:foo, 1, Type::Value.new)
+ assert_not attribute.has_been_read?
+ end
+
+ test "an attribute has been read when its value is calculated" do
+ attribute = Attribute.from_database(:foo, 1, Type::Value.new)
+ attribute.value
+ assert attribute.has_been_read?
+ end
+
+ test "an attribute can not be mutated if it has not been read,
+ and skips expensive calculations" do
+ type_which_raises_from_all_methods = Object.new
+ attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods)
+
+ assert_not attribute.changed_in_place_from?("bar")
+ end
end
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index dbe1eb48db..4ddf6e7ba0 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -58,7 +58,7 @@ module ActiveRecord
data = OverloadedType.new(non_existent_decimal: 1)
assert_equal BigDecimal.new(1), data.non_existent_decimal
- assert_raise ActiveRecord::UnknownAttributeError do
+ assert_raise ActiveModel::AttributeAssignment::UnknownAttributeError do
UnoverloadedType.new(non_existent_decimal: 1)
end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 670d94dc06..3ae4a6eade 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -288,7 +288,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
- [ :after_save, :block ]
+ [ :after_save, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
@@ -357,7 +362,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
- [ :after_save, :block ]
+ [ :after_save, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
@@ -408,7 +418,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_destroy, :string ],
[ :after_destroy, :proc ],
[ :after_destroy, :object ],
- [ :after_destroy, :block ]
+ [ :after_destroy, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index 330232cee2..4cbff564aa 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -55,7 +55,7 @@ class DateTimeTest < ActiveRecord::TestCase
now = DateTime.now
with_timezone_config default: :local do
task = Task.new starting: now
- assert now, task.starting
+ assert_equal now, task.starting
end
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index e9bc583bf4..b9db0d0123 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -40,7 +40,7 @@ class DefaultNumbersTest < ActiveRecord::TestCase
end
teardown do
- @connection.drop_table "default_numbers" if @connection.table_exists? 'default_numbers'
+ @connection.drop_table :default_numbers, if_exists: true
end
def test_default_positive_integer
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index ae4a8aab2c..192ba6f7cd 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -721,6 +721,14 @@ class DirtyTest < ActiveRecord::TestCase
assert record.save
end
+ test "mutating and then assigning doesn't remove the change" do
+ pirate = Pirate.create!(catchphrase: "arrrr")
+ pirate.catchphrase << " matey!"
+ pirate.catchphrase = "arrrr matey!"
+
+ assert pirate.catchphrase_changed?(from: "arrrr", to: "arrrr matey!")
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 925491acbd..0a577fa2f5 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -30,6 +30,9 @@ ARTest.connect
# Quote "type" if it's a reserved word for the current connection.
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type')
+# FIXME: Remove this when the deprecation cycle on TZ aware types by default ends.
+ActiveRecord::Base.time_zone_aware_types << :time
+
def current_adapter?(*types)
types.any? do |type|
ActiveRecord::ConnectionAdapters.const_defined?(type) &&
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index b3129a8984..a4bff0cbb9 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -403,6 +403,17 @@ module ActiveRecord
end
end
+ def test_drop_table_if_exists
+ connection.create_table(:testings)
+ assert connection.table_exists?(:testings)
+ connection.drop_table(:testings, if_exists: true)
+ assert_not connection.table_exists?(:testings)
+ end
+
+ def test_drop_table_if_exists_nothing_raised
+ assert_nothing_raised { connection.drop_table(:nonexistent, if_exists: true) }
+ end
+
private
def testing_table_with_only_foo_attribute
connection.create_table :testings, :id => false do |t|
@@ -426,7 +437,7 @@ module ActiveRecord
teardown do
[:wagons, :trains].each do |table|
- @connection.drop_table(table) if @connection.table_exists?(table)
+ @connection.drop_table table, if_exists: true
end
end
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
index 62186e13a5..4637970ce0 100644
--- a/activerecord/test/cases/migration/column_positioning_test.rb
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -3,7 +3,7 @@ require 'cases/helper'
module ActiveRecord
class Migration
class ColumnPositioningTest < ActiveRecord::TestCase
- attr_reader :connection, :table_name
+ attr_reader :connection
alias :conn :connection
def setup
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index f8b1bf8c9d..78d9dd90a3 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -29,8 +29,8 @@ module ActiveRecord
teardown do
if defined?(@connection)
- @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts'
- @connection.drop_table "rockets" if @connection.table_exists? 'rockets'
+ @connection.drop_table "astronauts", if_exists: true
+ @connection.drop_table "rockets", if_exists: true
end
end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index bba8897a0d..99de7db70c 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -10,8 +10,8 @@ module ActiveRecord
end
teardown do
- @connection.execute("drop table if exists testings")
- @connection.execute("drop table if exists testing_parents")
+ @connection.drop_table("testings") if @connection.table_exists? "testings"
+ @connection.drop_table("testing_parents") if @connection.table_exists? "testing_parents"
end
test "foreign keys can be created with the table" do
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index a018bac43d..3eef308428 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -86,8 +86,8 @@ module ActiveRecord
assert connection.table_exists? :felines
ensure
disable_extension!('uuid-ossp', connection)
- connection.drop_table :cats if connection.table_exists? :cats
- connection.drop_table :felines if connection.table_exists? :felines
+ connection.drop_table :cats, if_exists: true
+ connection.drop_table :felines, if_exists: true
end
end
end
diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb
index 8fd770abd1..24cba84a09 100644
--- a/activerecord/test/cases/migration/table_and_index_test.rb
+++ b/activerecord/test/cases/migration/table_and_index_test.rb
@@ -6,11 +6,11 @@ module ActiveRecord
def test_add_schema_info_respects_prefix_and_suffix
conn = ActiveRecord::Base.connection
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true)
# Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
ActiveRecord::Base.table_name_prefix = 'p_'
ActiveRecord::Base.table_name_suffix = '_s'
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true)
conn.initialize_schema_migrations_table
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 7c2d3e81d6..a5d7cc2dff 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -90,7 +90,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_migration_detection_without_schema_migration_table
- ActiveRecord::Base.connection.drop_table('schema_migrations') if ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ ActiveRecord::Base.connection.drop_table 'schema_migrations', if_exists: true
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index 14d4ef457d..4aaf6f8b5f 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -250,8 +250,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
- assert topic.bonus_time.utc?
+ assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time
+ assert_not topic.bonus_time.utc?
end
end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 5c7e8a65d2..8bc42b8890 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -672,7 +672,7 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_not_assign_destroy_key_to_a_record
- assert_nothing_raised ActiveRecord::UnknownAttributeError do
+ assert_nothing_raised ActiveModel::AttributeAssignment::UnknownAttributeError do
@pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }})
end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index d6816041bc..2803ad2de0 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -356,6 +356,16 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal("David", topic_reloaded.author_name)
end
+ def test_update_attribute_does_not_run_sql_if_attribute_is_not_changed
+ klass = Class.new(Topic) do
+ def self.name; 'Topic'; end
+ end
+ topic = klass.create(title: 'Another New Topic')
+ assert_queries(0) do
+ topic.update_attribute(:title, 'Another New Topic')
+ end
+ end
+
def test_delete
topic = Topic.find(1)
assert_equal topic, topic.delete, 'topic.delete did not return self'
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 751eccc015..4b668f84dd 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -210,7 +210,7 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute("DROP TABLE IF EXISTS barcodes")
+ @connection.drop_table(:barcodes) if @connection.table_exists? :barcodes
end
def test_any_type_primary_key
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 1d6ae2f67f..ad09340518 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -125,14 +125,11 @@ module ActiveRecord
end
def test_crazy_object
- crazy = Class.new.new
- expected = "'#{YAML.dump(crazy)}'"
- assert_equal expected, @quoter.quote(crazy, nil)
- end
-
- def test_crazy_object_calls_quote_string
- crazy = Class.new { def initialize; @lol = 'lo\l' end }.new
- assert_match "lo\\\\l", @quoter.quote(crazy, nil)
+ crazy = Object.new
+ e = assert_raises(TypeError) do
+ @quoter.quote(crazy, nil)
+ end
+ assert_equal "can't quote Object", e.message
end
def test_quote_string_no_column
diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb
index 0cc081fced..8f62014622 100644
--- a/activerecord/test/cases/relation/predicate_builder_test.rb
+++ b/activerecord/test/cases/relation/predicate_builder_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source))
end)
- assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql
+ assert_match %r{["`]topics["`]\.["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql
ensure
Topic.reset_column_information
end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index 619055f1e7..0f9019bb1b 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -42,7 +42,7 @@ module ActiveRecord
end
def test_association_not_eq
- expected = Comment.arel_table[@name].not_eq('hello')
+ expected = Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new)
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
assert_equal(expected.to_sql, relation.where_values.first.to_sql)
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index b0573579da..537937decd 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -28,6 +28,14 @@ module ActiveRecord
}
end
+ def test_where_copies_bind_params_in_the_right_order
+ author = authors(:david)
+ posts = author.posts.where.not(id: 1)
+ joined = Post.where(id: posts, title: posts.first.title)
+
+ assert_equal joined, [posts.first]
+ end
+
def test_where_copies_arel_bind_params
chef = Chef.create!
CakeDesigner.create!(chef: chef)
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 9631ea79be..d272b9b929 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -857,6 +857,12 @@ class RelationTest < ActiveRecord::TestCase
assert ! fake.exists?(authors(:david).id)
end
+ def test_exists_uses_existing_scope
+ post = authors(:david).posts.first
+ authors = Author.includes(:posts).where(name: "David", posts: { id: post.id })
+ assert authors.exists?(authors(:david).id)
+ end
+
def test_last
authors = Author.all
assert_equal authors(:bob), authors.last
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index b52c66356c..c8ea702488 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -204,25 +204,25 @@ class SchemaDumperTest < ActiveRecord::TestCase
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_schema_dump_should_add_default_value_for_mysql_text_field
output = standard_dump
- assert_match %r{t.text\s+"body",\s+limit: 65535,\s+null: false$}, output
+ assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output
end
def test_schema_dump_includes_length_for_mysql_binary_fields
output = standard_dump
- assert_match %r{t.binary\s+"var_binary",\s+limit: 255$}, output
- assert_match %r{t.binary\s+"var_binary_large",\s+limit: 4095$}, output
+ assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output
+ assert_match %r{t\.binary\s+"var_binary_large",\s+limit: 4095$}, output
end
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
- assert_match %r{t.binary\s+"tiny_blob",\s+limit: 255$}, output
- assert_match %r{t.binary\s+"normal_blob",\s+limit: 65535$}, output
- assert_match %r{t.binary\s+"medium_blob",\s+limit: 16777215$}, output
- assert_match %r{t.binary\s+"long_blob",\s+limit: 4294967295$}, output
- assert_match %r{t.text\s+"tiny_text",\s+limit: 255$}, output
- assert_match %r{t.text\s+"normal_text",\s+limit: 65535$}, output
- assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output
- assert_match %r{t.text\s+"long_text",\s+limit: 4294967295$}, output
+ assert_match %r{t\.binary\s+"tiny_blob",\s+limit: 255$}, output
+ assert_match %r{t\.binary\s+"normal_blob",\s+limit: 65535$}, output
+ assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output
+ assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output
+ assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output
+ assert_match %r{t\.text\s+"normal_text",\s+limit: 65535$}, output
+ assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output
+ assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output
end
def test_schema_dumps_index_type
@@ -235,19 +235,19 @@ class SchemaDumperTest < ActiveRecord::TestCase
if mysql_56?
def test_schema_dump_includes_datetime_precision
output = standard_dump
- assert_match %r{t.datetime\s+"written_on",\s+precision: 6$}, output
+ assert_match %r{t\.datetime\s+"written_on",\s+precision: 6$}, output
end
end
def test_schema_dump_includes_decimal_options
output = dump_all_table_schema([/^[^n]/])
- assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output
+ assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output
end
if current_adapter?(:PostgreSQLAdapter)
def test_schema_dump_includes_bigint_default
output = standard_dump
- assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
+ assert_match %r{t\.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -271,11 +271,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = standard_dump
# Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
if current_adapter?(:OracleAdapter)
- assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38}, output
+ assert_match %r{t\.integer\s+"atoms_in_universe",\s+precision: 38}, output
elsif current_adapter?(:FbAdapter)
- assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 18}, output
+ assert_match %r{t\.integer\s+"atoms_in_universe",\s+precision: 18}, output
else
- assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55}, output
+ assert_match %r{t\.decimal\s+"atoms_in_universe",\s+precision: 55}, output
end
end
@@ -284,7 +284,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n})
assert_not_nil(match, "goofy_string_id table not found")
assert_match %r(id: false), match[1], "no table id not preserved"
- assert_match %r{t.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved"
+ assert_match %r{t\.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved"
end
def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 185fc22e98..f185cda263 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -4,7 +4,6 @@ require 'models/pet'
require 'models/topic'
class TransactionCallbacksTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
fixtures :topics, :owners, :pets
class ReplyWithCallbacks < ActiveRecord::Base
@@ -200,21 +199,21 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_call_after_rollback_when_commit_fails
- @first.class.connection.singleton_class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
- begin
- @first.class.connection.singleton_class.class_eval do
- def commit_db_transaction; raise "boom!"; end
- end
+ @first.after_commit_block { |r| r.history << :after_commit }
+ @first.after_rollback_block { |r| r.history << :after_rollback }
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
+ assert_raises RuntimeError do
+ @first.transaction do
+ tx = @first.class.connection.transaction_manager.current_transaction
+ def tx.commit
+ raise
+ end
- assert !@first.save rescue nil
- assert_equal [:after_rollback], @first.history
- ensure
- @first.class.connection.singleton_class.send(:remove_method, :commit_db_transaction)
- @first.class.connection.singleton_class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
+ @first.save
+ end
end
+
+ assert_equal [:after_rollback], @first.history
end
def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index ff956b7680..0c60f0690c 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -60,55 +60,68 @@ module ActiveRecord
test "values below int min value are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("-2147483649")
+ Integer.new.type_cast_for_database(-2147483649)
end
end
test "values above int max value are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("2147483648")
+ Integer.new.type_cast_for_database(2147483648)
end
end
test "very small numbers are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("-9999999999999999999999999999999")
+ Integer.new.type_cast_for_database(-9999999999999999999999999999999)
end
end
test "very large numbers are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("9999999999999999999999999999999")
+ Integer.new.type_cast_for_database(9999999999999999999999999999999)
end
end
test "normal numbers are in range" do
type = Integer.new
- assert_equal(0, type.type_cast_from_user("0"))
- assert_equal(-1, type.type_cast_from_user("-1"))
- assert_equal(1, type.type_cast_from_user("1"))
+ assert_equal(0, type.type_cast_for_database(0))
+ assert_equal(-1, type.type_cast_for_database(-1))
+ assert_equal(1, type.type_cast_for_database(1))
end
test "int max value is in range" do
- assert_equal(2147483647, Integer.new.type_cast_from_user("2147483647"))
+ assert_equal(2147483647, Integer.new.type_cast_for_database(2147483647))
end
test "int min value is in range" do
- assert_equal(-2147483648, Integer.new.type_cast_from_user("-2147483648"))
+ assert_equal(-2147483648, Integer.new.type_cast_for_database(-2147483648))
end
test "columns with a larger limit have larger ranges" do
type = Integer.new(limit: 8)
- assert_equal(9223372036854775807, type.type_cast_from_user("9223372036854775807"))
- assert_equal(-9223372036854775808, type.type_cast_from_user("-9223372036854775808"))
+ assert_equal(9223372036854775807, type.type_cast_for_database(9223372036854775807))
+ assert_equal(-9223372036854775808, type.type_cast_for_database(-9223372036854775808))
assert_raises(::RangeError) do
- type.type_cast_from_user("-9999999999999999999999999999999")
+ type.type_cast_for_database(-9999999999999999999999999999999)
end
assert_raises(::RangeError) do
- type.type_cast_from_user("9999999999999999999999999999999")
+ type.type_cast_for_database(9999999999999999999999999999999)
end
end
+
+ test "values which are out of range can be re-assigned" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'posts'
+ attribute :foo, Type::Integer.new
+ end
+ model = klass.new
+
+ model.foo = 2147483648
+ model.foo = 1
+
+ assert_equal 1, model.foo
+ end
end
end
end
diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb
index b6f673109e..4b8e2fb518 100644
--- a/activerecord/test/cases/type/unsigned_integer_test.rb
+++ b/activerecord/test/cases/type/unsigned_integer_test.rb
@@ -4,12 +4,12 @@ module ActiveRecord
module Type
class UnsignedIntegerTest < ActiveRecord::TestCase
test "unsigned int max value is in range" do
- assert_equal(4294967295, UnsignedInteger.new.type_cast_from_user("4294967295"))
+ assert_equal(4294967295, UnsignedInteger.new.type_cast_for_database(4294967295))
end
test "minus value is out of range" do
assert_raises(::RangeError) do
- UnsignedInteger.new.type_cast_from_user("-1")
+ UnsignedInteger.new.type_cast_for_database(-1)
end
end
end
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
index 73e92addfe..d35d34ff2d 100644
--- a/activerecord/test/cases/types_test.rb
+++ b/activerecord/test/cases/types_test.rb
@@ -117,6 +117,23 @@ module ActiveRecord
assert_equal Encoding::ASCII_8BIT, type_cast.encoding
end
end
+
+ def test_attributes_which_are_invalid_for_database_can_still_be_reassigned
+ type_which_cannot_go_to_the_database = Type::Value.new
+ def type_which_cannot_go_to_the_database.type_cast_for_database(*)
+ raise
+ end
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'posts'
+ attribute :foo, type_which_cannot_go_to_the_database
+ end
+ model = klass.new
+
+ model.foo = "foo"
+ model.foo = "bar"
+
+ assert_equal "bar", model.foo
+ end
end
end
end
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index c34e7d5a30..b30b50f597 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -19,7 +19,7 @@ class XmlSerializationTest < ActiveRecord::TestCase
def test_should_serialize_default_root_with_namespace
@xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact"
- assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, @xml
+ assert_match %r{^<contact xmlns="http://xml\.rubyonrails\.org/contact">}, @xml
assert_match %r{</contact>$}, @xml
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index f60689b2b5..2caf0b27ee 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,15 @@
+* Duplicate frozen array when assigning it to a HashWithIndifferentAccess so
+ that it doesn't raise a `RuntimeError` when calling `map!` on it in `convert_value`.
+
+ Fixes #18550.
+
+ *Aditya Kapoor*
+
+* Add missing time zone definitions for Russian Federation and sync them
+ with `zone.tab` file from tzdata version 2014j (latest).
+
+ *Andrey Novikov*
+
* Add `SecureRandom.base58` for generation of random base58 strings.
*Matthew Draper*, *Guillermo Iguaran*
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 45b89d2705..ca66d806ef 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -21,7 +21,7 @@ class Array
# %w( a b c ).to(-10) # => []
def to(position)
if position >= 0
- first position + 1
+ take position + 1
else
self[0..position]
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index ef32817f55..98716383f4 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -1,6 +1,8 @@
require 'active_support/duration'
require 'active_support/core_ext/time/calculations'
require 'active_support/core_ext/time/acts_like'
+require 'active_support/core_ext/date/calculations'
+require 'active_support/core_ext/date/acts_like'
class Numeric
# Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 2818b8d68b..5a64fc52cc 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -91,7 +91,7 @@ module ActiveSupport
reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
- to_sentence(:locale => :en)
+ to_sentence(locale: ::I18n.default_locale)
end
def as_json(options = nil) #:nodoc:
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 1468c62151..4f71f13971 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -267,7 +267,7 @@ module ActiveSupport
value.nested_under_indifferent_access
end
elsif value.is_a?(Array)
- unless options[:for] == :assignment
+ if options[:for] != :assignment || value.frozen?
value = value.dup
end
value.map! { |e| convert_value(e, options) }
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 74b3a7c2a9..fe8a2ac9ba 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -22,49 +22,49 @@ module ActiveSupport
# pluralized using rules defined for that language. By default,
# this parameter is set to <tt>:en</tt>.
#
- # 'post'.pluralize # => "posts"
- # 'octopus'.pluralize # => "octopi"
- # 'sheep'.pluralize # => "sheep"
- # 'words'.pluralize # => "words"
- # 'CamelOctopus'.pluralize # => "CamelOctopi"
- # 'ley'.pluralize(:es) # => "leyes"
+ # pluralize('post') # => "posts"
+ # pluralize('octopus') # => "octopi"
+ # pluralize('sheep') # => "sheep"
+ # pluralize('words') # => "words"
+ # pluralize('CamelOctopus') # => "CamelOctopi"
+ # pluralize('ley', :es) # => "leyes"
def pluralize(word, locale = :en)
apply_inflections(word, inflections(locale).plurals)
end
- # The reverse of +pluralize+, returns the singular form of a word in a
+ # The reverse of #pluralize, returns the singular form of a word in a
# string.
#
# If passed an optional +locale+ parameter, the word will be
# singularized using rules defined for that language. By default,
# this parameter is set to <tt>:en</tt>.
#
- # 'posts'.singularize # => "post"
- # 'octopi'.singularize # => "octopus"
- # 'sheep'.singularize # => "sheep"
- # 'word'.singularize # => "word"
- # 'CamelOctopi'.singularize # => "CamelOctopus"
- # 'leyes'.singularize(:es) # => "ley"
+ # singularize('posts') # => "post"
+ # singularize('octopi') # => "octopus"
+ # singularize('sheep') # => "sheep"
+ # singularize('word') # => "word"
+ # singularize('CamelOctopi') # => "CamelOctopus"
+ # singularize('leyes', :es) # => "ley"
def singularize(word, locale = :en)
apply_inflections(word, inflections(locale).singulars)
end
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument
- # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
+ # Converts strings to UpperCamelCase.
+ # If the +uppercase_first_letter+ parameter is set to false, then produces
# lowerCamelCase.
#
- # +camelize+ will also convert '/' to '::' which is useful for converting
+ # Also converts '/' to '::' which is useful for converting
# paths to namespaces.
#
- # 'active_model'.camelize # => "ActiveModel"
- # 'active_model'.camelize(:lower) # => "activeModel"
- # 'active_model/errors'.camelize # => "ActiveModel::Errors"
- # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
+ # camelize('active_model') # => "ActiveModel"
+ # camelize('active_model', false) # => "activeModel"
+ # camelize('active_model/errors') # => "ActiveModel::Errors"
+ # camelize('active_model/errors', false) # => "activeModel::Errors"
#
# As a rule of thumb you can think of +camelize+ as the inverse of
- # +underscore+, though there are cases where that does not hold:
+ # #underscore, though there are cases where that does not hold:
#
- # 'SSLError'.underscore.camelize # => "SslError"
+ # camelize(underscore('SSLError')) # => "SslError"
def camelize(term, uppercase_first_letter = true)
string = term.to_s
if uppercase_first_letter
@@ -81,13 +81,13 @@ module ActiveSupport
#
# Changes '::' to '/' to convert namespaces to paths.
#
- # 'ActiveModel'.underscore # => "active_model"
- # 'ActiveModel::Errors'.underscore # => "active_model/errors"
+ # underscore('ActiveModel') # => "active_model"
+ # underscore('ActiveModel::Errors') # => "active_model/errors"
#
# As a rule of thumb you can think of +underscore+ as the inverse of
- # +camelize+, though there are cases where that does not hold:
+ # #camelize, though there are cases where that does not hold:
#
- # 'SSLError'.underscore.camelize # => "SslError"
+ # camelize(underscore('SSLError')) # => "SslError"
def underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
word = camel_cased_word.to_s.gsub(/::/, '/')
@@ -101,14 +101,14 @@ module ActiveSupport
# Tweaks an attribute name for display to end users.
#
- # Specifically, +humanize+ performs these transformations:
+ # Specifically, performs these transformations:
#
- # * Applies human inflection rules to the argument.
- # * Deletes leading underscores, if any.
- # * Removes a "_id" suffix if present.
- # * Replaces underscores with spaces, if any.
- # * Downcases all words except acronyms.
- # * Capitalizes the first word.
+ # * Applies human inflection rules to the argument.
+ # * Deletes leading underscores, if any.
+ # * Removes a "_id" suffix if present.
+ # * Replaces underscores with spaces, if any.
+ # * Downcases all words except acronyms.
+ # * Capitalizes the first word.
#
# The capitalization of the first word can be turned off by setting the
# +:capitalize+ option to false (default is true).
@@ -148,34 +148,34 @@ module ActiveSupport
#
# +titleize+ is also aliased as +titlecase+.
#
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
- # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
- # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
def titleize(word)
humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
end
- # Create the name of a table like Rails does for models to table names. This
- # method uses the +pluralize+ method on the last word in the string.
+ # Creates the name of a table like Rails does for models to table names.
+ # This method uses the #pluralize method on the last word in the string.
#
- # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
- # 'egg_and_ham'.tableize # => "egg_and_hams"
- # 'fancyCategory'.tableize # => "fancy_categories"
+ # tableize('RawScaledScorer') # => "raw_scaled_scorers"
+ # tableize('egg_and_ham') # => "egg_and_hams"
+ # tableize('fancyCategory') # => "fancy_categories"
def tableize(class_name)
pluralize(underscore(class_name))
end
- # Create a class name from a plural table name like Rails does for table
+ # Creates a class name from a plural table name like Rails does for table
# names to models. Note that this returns a string and not a Class (To
- # convert to an actual class follow +classify+ with +constantize+).
+ # convert to an actual class follow +classify+ with #constantize).
#
- # 'egg_and_hams'.classify # => "EggAndHam"
- # 'posts'.classify # => "Post"
+ # classify('egg_and_hams') # => "EggAndHam"
+ # classify('posts') # => "Post"
#
# Singular names are not handled correctly:
#
- # 'calculus'.classify # => "Calculu"
+ # classify('calculus') # => "Calculu"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
@@ -183,19 +183,19 @@ module ActiveSupport
# Replaces underscores with dashes in the string.
#
- # 'puni_puni'.dasherize # => "puni-puni"
+ # dasherize('puni_puni') # => "puni-puni"
def dasherize(underscored_word)
underscored_word.tr('_', '-')
end
# Removes the module part from the expression in the string.
#
- # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
- # 'Inflections'.demodulize # => "Inflections"
- # '::Inflections'.demodulize # => "Inflections"
- # ''.demodulize # => ""
+ # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections"
+ # demodulize('Inflections') # => "Inflections"
+ # demodulize('::Inflections') # => "Inflections"
+ # demodulize('') # => ""
#
- # See also +deconstantize+.
+ # See also #deconstantize.
def demodulize(path)
path = path.to_s
if i = path.rindex('::')
@@ -207,13 +207,13 @@ module ActiveSupport
# Removes the rightmost segment from the constant expression in the string.
#
- # 'Net::HTTP'.deconstantize # => "Net"
- # '::Net::HTTP'.deconstantize # => "::Net"
- # 'String'.deconstantize # => ""
- # '::String'.deconstantize # => ""
- # ''.deconstantize # => ""
+ # deconstantize('Net::HTTP') # => "Net"
+ # deconstantize('::Net::HTTP') # => "::Net"
+ # deconstantize('String') # => ""
+ # deconstantize('::String') # => ""
+ # deconstantize('') # => ""
#
- # See also +demodulize+.
+ # See also #demodulize.
def deconstantize(path)
path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
end
@@ -222,9 +222,9 @@ module ActiveSupport
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # 'Message'.foreign_key # => "message_id"
- # 'Message'.foreign_key(false) # => "messageid"
- # 'Admin::Post'.foreign_key # => "post_id"
+ # foreign_key('Message') # => "message_id"
+ # foreign_key('Message', false) # => "messageid"
+ # foreign_key('Admin::Post') # => "post_id"
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
end
@@ -280,8 +280,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # 'Module'.safe_constantize # => Module
- # 'Test::Unit'.safe_constantize # => Test::Unit
+ # safe_constantize('Module') # => Module
+ # safe_constantize('Test::Unit') # => Test::Unit
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
@@ -290,16 +290,16 @@ module ActiveSupport
# C = 'outside'
# module M
# C = 'inside'
- # C # => 'inside'
- # 'C'.safe_constantize # => 'outside', same as ::C
+ # C # => 'inside'
+ # safe_constantize('C') # => 'outside', same as ::C
# end
#
# +nil+ is returned when the name is not in CamelCase or the constant (or
# part of it) is unknown.
#
- # 'blargle'.safe_constantize # => nil
- # 'UnknownModule'.safe_constantize # => nil
- # 'UnknownModule::Foo::Bar'.safe_constantize # => nil
+ # safe_constantize('blargle') # => nil
+ # safe_constantize('UnknownModule') # => nil
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
def safe_constantize(camel_cased_word)
constantize(camel_cased_word)
rescue NameError => e
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 1cde417fc5..edea142e82 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -67,17 +67,8 @@ module ActiveSupport
# Replaces special characters in a string so that it may be used as part of
# a 'pretty' URL.
#
- # class Person
- # def to_param
- # "#{id}-#{name.parameterize}"
- # end
- # end
- #
- # @person = Person.find(1)
- # # => #<Person id: 1, name: "Donald E. Knuth">
- #
- # <%= link_to(@person.name, person_path(@person)) %>
- # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
+ # parameterize("Donald E. Knuth") # => "donald-e-knuth"
+ # parameterize("^trés|Jolie-- ") # => "tres-jolie"
def parameterize(string, sep = '-')
# replace accented chars with their ascii equivalents
parameterized_string = transliterate(string)
@@ -92,6 +83,5 @@ module ActiveSupport
end
parameterized_string.downcase
end
-
end
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 17629eabb3..728b53849d 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -111,9 +111,11 @@ module ActiveSupport
"Jerusalem" => "Asia/Jerusalem",
"Harare" => "Africa/Harare",
"Pretoria" => "Africa/Johannesburg",
+ "Kaliningrad" => "Europe/Kaliningrad",
"Moscow" => "Europe/Moscow",
"St. Petersburg" => "Europe/Moscow",
- "Volgograd" => "Europe/Moscow",
+ "Volgograd" => "Europe/Volgograd",
+ "Samara" => "Europe/Samara",
"Kuwait" => "Asia/Kuwait",
"Riyadh" => "Asia/Riyadh",
"Nairobi" => "Africa/Nairobi",
@@ -170,6 +172,7 @@ module ActiveSupport
"Guam" => "Pacific/Guam",
"Port Moresby" => "Pacific/Port_Moresby",
"Magadan" => "Asia/Magadan",
+ "Srednekolymsk" => "Asia/Srednekolymsk",
"Solomon Is." => "Pacific/Guadalcanal",
"New Caledonia" => "Pacific/Noumea",
"Fiji" => "Pacific/Fiji",
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 2b893c7cd0..c283b546e6 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -70,6 +70,15 @@ class DurationTest < ActiveSupport::TestCase
assert_equal '14 days', 1.fortnight.inspect
end
+ def test_inspect_locale
+ current_locale = I18n.default_locale
+ I18n.default_locale = :de
+ I18n.backend.store_translations(:de, { support: { array: { last_word_connector: ' und ' } } })
+ assert_equal '10 years, 1 month und 1 day', (10.years + 1.month + 1.day).inspect
+ ensure
+ I18n.default_locale = current_locale
+ end
+
def test_minus_with_duration_does_not_break_subtraction_of_date_from_date
assert_nothing_raised { Date.today - Date.today }
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 3d2f50ce49..e10bee5e00 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -1549,6 +1549,14 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_not_same hash_wia, hash_wia.with_indifferent_access
end
+
+ def test_allows_setting_frozen_array_values_with_indifferent_access
+ value = [1, 2, 3].freeze
+ hash = HashWithIndifferentAccess.new
+ hash[:key] = value
+ assert_equal hash[:key], value
+ end
+
def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
hash = Hash.new(3)
hash_wia = hash.with_indifferent_access
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
index 843994147b..1facd691fa 100644
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -7,4 +7,5 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
hash.reverse_merge! key: :new_value
assert_equal :old_value, hash[:key]
end
-end \ No newline at end of file
+
+end
diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png
index 117a78a39f..c489e4c00e 100644
--- a/guides/assets/images/getting_started/article_with_comments.png
+++ b/guides/assets/images/getting_started/article_with_comments.png
Binary files differ
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index 66bbb15afb..b295d9d21f 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -12,10 +12,10 @@ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
- create_table :posts do |t|
+ create_table :posts, force: true do |t|
end
- create_table :comments do |t|
+ create_table :comments, force: true do |t|
t.integer :post_id
end
end
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index d95354e12d..9557f0b7c5 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -21,10 +21,10 @@ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
- create_table :posts do |t|
+ create_table :posts, force: true do |t|
end
- create_table :comments do |t|
+ create_table :comments, force: true do |t|
t.integer :post_id
end
end
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index 50a791cda5..554d94ad50 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -25,7 +25,7 @@ HTML
def paragraph(text)
if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/
convert_notes(text)
- elsif text.include?('DO NOT READ THIS FILE IN GITHUB')
+ elsif text.include?('DO NOT READ THIS FILE ON GITHUB')
elsif text =~ /^\[<sup>(\d+)\]:<\/sup> (.+)$/
linkback = %(<a href="#footnote-#{$1}-ref"><sup>#{$1}</sup></a>)
%(<p class="footnote" id="footnote-#{$1}">#{linkback} #{$2}</p>)
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index 019da08687..be00087f63 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails 2.2 Release Notes
===============================
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 4ac1529e76..328656f4a4 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails 2.3 Release Notes
===============================
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 3d7966e50b..9ad32e8168 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails 3.0 Release Notes
===============================
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index 8728750966..e187e5f9ab 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails 3.1 Release Notes
===============================
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index 0b28aac9ce..6ddf77d9c0 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails 3.2 Release Notes
===============================
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index 5f52c33746..67f4a3c02c 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails 4.0 Release Notes
===============================
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index dbc151c0ca..8d5557be6e 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails 4.1 Release Notes
===============================
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index faff1add9f..366d9d26b4 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails 4.2 Release Notes
===============================
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 36d1b6de83..80000baf66 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Action Controller Overview
==========================
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index c586675ee5..e55ff16495 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Action Mailer Basics
====================
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index a6bde4f517..665a2b71ff 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Action View Overview
====================
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 31c9406d5c..4d1625b28d 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Job Basics
=================
@@ -78,7 +78,8 @@ end
Enqueue a job like so:
```ruby
-# Enqueue a job to be performed as soon the queueing system is free.
+# Enqueue a job to be performed as soon the queueing system is
+# free.
MyJob.perform_later record
```
@@ -114,8 +115,9 @@ You can easily set your queueing backend:
# config/application.rb
module YourApp
class Application < Rails::Application
- # Be sure to have the adapter's gem in your Gemfile and follow
- # the adapter's specific installation and deployment instructions.
+ # Be sure to have the adapter's gem in your Gemfile
+ # and follow the adapter's specific installation
+ # and deployment instructions.
config.active_job.queue_adapter = :sidekiq
end
end
@@ -153,8 +155,8 @@ class GuestsCleanupJob < ActiveJob::Base
end
# Now your job will run on queue production_low_priority on your
-# production environment and on staging_low_priority on your staging
-# environment
+# production environment and on staging_low_priority
+# on your staging environment
```
The default queue name prefix delimiter is '\_'. This can be changed by setting
@@ -176,8 +178,8 @@ class GuestsCleanupJob < ActiveJob::Base
end
# Now your job will run on queue production.low_priority on your
-# production environment and on staging.low_priority on your staging
-# environment
+# production environment and on staging.low_priority
+# on your staging environment
```
If you want more control on what queue a job will be run you can pass a `:queue`
@@ -295,7 +297,7 @@ end
```
This works with any class that mixes in `GlobalID::Identification`, which
-by default has been mixed into Active Model classes.
+by default has been mixed into Active Record classes.
Exceptions
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 8dee1cc5ec..4b2bfaee2f 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Model Basics
===================
@@ -396,7 +396,7 @@ class Person
end
```
-With the `to_xml` you have a XML representing the model.
+With the `to_xml` you have an XML representing the model.
```ruby
person = Person.new
@@ -405,7 +405,7 @@ person.name = "Bob"
person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n"
```
-From a XML string you define the attributes of the model.
+From an XML string you define the attributes of the model.
You need to have the `attributes=` method defined on your class:
```ruby
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 9d2ba328ea..a5196e481e 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Record Basics
====================
@@ -62,7 +62,7 @@ Convention over Configuration in Active Record
When writing applications using other programming languages or frameworks, it
may be necessary to write a lot of configuration code. This is particularly true
for ORM frameworks in general. However, if you follow the conventions adopted by
-Rails, you'll need to write very little configuration (in some case no
+Rails, you'll need to write very little configuration (in some cases no
configuration at all) when creating Active Record models. The idea is that if
you configure your applications in the very same way most of the time then this
should be the default way. Thus, explicit configuration would be needed
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 9d3a8c3af6..e65ab802c0 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Record Callbacks
=======================
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 8ae282bad2..97cabc1728 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Record Migrations
========================
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index fa0f31cbbd..4d9c1776f4 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Record and PostgreSQL
============================
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index f8c64cbd0c..373a98bb85 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Record Query Interface
=============================
@@ -257,6 +257,12 @@ It is equivalent to writing:
Client.where(first_name: 'Lifo').take
```
+The SQL equivalent of the above is:
+
+```sql
+SELECT * FROM clients WHERE (clients.first_name = 'Lifo') LIMIT 1
+```
+
The `find_by!` method behaves exactly like `find_by`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. For example:
```ruby
@@ -1128,7 +1134,7 @@ This would generate a query which contains a `LEFT OUTER JOIN` whereas the
If there was no `where` condition, this would generate the normal set of two queries.
NOTE: Using `where` like this will only work when you pass it a Hash. For
-SQL-fragments you need use `references` to force joined tables:
+SQL-fragments you need to use `references` to force joined tables:
```ruby
Article.includes(:comments).where("comments.visible = true").references(:comments)
@@ -1269,7 +1275,7 @@ User.active.where(state: 'finished')
# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'
```
-If we do want the `last where clause` to win then `Relation#merge` can
+If we do want the last `where` clause to win then `Relation#merge` can
be used.
```ruby
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 8c832bafff..67cc6a4db3 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Record Validations
=========================
@@ -227,8 +227,26 @@ end
```
We'll cover validation errors in greater depth in the [Working with Validation
-Errors](#working-with-validation-errors) section. For now, let's turn to the
-built-in validation helpers that Rails provides by default.
+Errors](#working-with-validation-errors) section.
+
+### `errors.details`
+
+To check what validator type was used on invalid attribute, you can use
+`errors.details[:attribute]`. It returns array of hashes where under `:error`
+ key you will find symbol of used validator.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true
+end
+
+>> person = Person.new
+>> person.valid?
+>> person.errors.details[:name] #=> [{error: :blank}]
+```
+
+Using `details` with custom validators are covered in the [Working with
+Validation Errors](#working-with-validation-errors) section.
Validation Helpers
------------------
@@ -452,7 +470,7 @@ point number. To specify that only integral numbers are allowed set
If you set `:only_integer` to `true`, then it will use the
```ruby
-/\A[+-]?\d+\Z/
+/\A[+-]?\d+\z/
```
regular expression to validate the attribute's value. Otherwise, it will try to
@@ -1074,6 +1092,42 @@ Another way to do this is using `[]=` setter
# => ["Name cannot contain the characters !@#%*()_-+="]
```
+### `errors.details`
+
+You can add validator type to details hash when using `errors.add` method.
+
+```ruby
+ class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors.add(:name, :invalid_characters)
+ end
+ end
+
+ person = Person.create(name: "!@#")
+
+ person.errors.details[:name]
+ # => [{error: :invalid_characters}]
+```
+
+To improve error details to contain not allowed characters set, you can
+pass additional options to `errors.add` method.
+
+```ruby
+ class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=")
+ end
+ end
+
+ person = Person.create(name: "!@#")
+
+ person.errors.details[:name]
+ # => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}]
+```
+
+All built in Rails validators populate details hash with corresponding
+validator types.
+
### `errors[:base]`
You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message.
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index ba839e1723..0fbd6ed7e1 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Support Core Extensions
==============================
@@ -467,7 +467,7 @@ C.new(0, 1).instance_variable_names # => ["@x", "@y"]
NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`.
-### Silencing Warnings, Streams, and Exceptions
+### Silencing Warnings and Exceptions
The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards:
@@ -475,26 +475,10 @@ The methods `silence_warnings` and `enable_warnings` change the value of `$VERBO
silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
```
-You can silence any stream while a block runs with `silence_stream`:
-
-```ruby
-silence_stream(STDOUT) do
- # STDOUT is silent here
-end
-```
-
-The `quietly` method addresses the common use case where you want to silence STDOUT and STDERR, even in subprocesses:
-
-```ruby
-quietly { system 'bundle install' }
-```
-
-For example, the railties test suite uses that one in a few places to prevent command messages from being echoed intermixed with the progress status.
-
Silencing exceptions is also possible with `suppress`. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is `kind_of?` any of the arguments, `suppress` captures it and returns silently. Otherwise the exception is reraised:
```ruby
-# If the user is locked the increment is lost, no big deal.
+# If the user is locked, the increment is lost, no big deal.
suppress(ActiveRecord::StaleObjectError) do
current_user.increment! :visits
end
@@ -3813,50 +3797,6 @@ WARNING. If the argument is an `IO` it needs to respond to `rewind` to be able t
NOTE: Defined in `active_support/core_ext/marshal.rb`.
-Extensions to `Logger`
-----------------------
-
-### `around_[level]`
-
-Takes two arguments, a `before_message` and `after_message` and calls the current level method on the `Logger` instance, passing in the `before_message`, then the specified message, then the `after_message`:
-
-```ruby
-logger = Logger.new("log/development.log")
-logger.around_info("before", "after") { |logger| logger.info("during") }
-```
-
-### `silence`
-
-Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal.
-
-```ruby
-logger = Logger.new("log/development.log")
-logger.silence(Logger::INFO) do
- logger.debug("In space, no one can hear you scream.")
- logger.info("Scream all you want, small mailman!")
-end
-```
-
-### `datetime_format=`
-
-Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a `datetime_format` method then this is ignored.
-
-```ruby
-class Logger::FormatWithTime < Logger::Formatter
- cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" }
-
- def self.call(severity, timestamp, progname, msg)
- "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n"
- end
-end
-
-logger = Logger.new("log/development.log")
-logger.formatter = Logger::FormatWithTime
-logger.info("<- is the current time")
-```
-
-NOTE: Defined in `active_support/core_ext/logger.rb`.
-
Extensions to `NameError`
-------------------------
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 0aa74e387d..9d9f40e956 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Support Instrumentation
==============================
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index d481700709..b385bdbe83 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
API Documentation Guidelines
============================
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 64d1c31083..52ea605a72 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
The Asset Pipeline
==================
@@ -149,7 +149,7 @@ clients to fetch them again, even when the content of those assets has not chang
Fingerprinting fixes these problems by avoiding query strings, and by ensuring
that filenames are consistent based on their content.
-Fingerprinting is enabled by default for production and disabled for all other
+Fingerprinting is enabled by default for both the development and production
environments. You can enable or disable it in your configuration through the
`config.assets.digest` option.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 95c7e747ef..dab885a8fb 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Active Record Associations
==========================
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index 489ea681e2..202dc601a7 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Autoloading and Reloading Constants
===================================
@@ -80,7 +80,8 @@ end
```
The *nesting* at any given place is the collection of enclosing nested class and
-module objects outwards. For example, in the previous example, the nesting at
+module objects outwards. The nesting at any given place can be inspected with
+`Module.nesting`. For example, in the previous example, the nesting at
(1) is
```ruby
@@ -113,6 +114,16 @@ certain nesting does not necessarily correlate with the namespaces at the spot.
Even more, they are totally independent, take for instance
```ruby
+module X
+ module Y
+ end
+end
+
+module A
+ module B
+ end
+end
+
module X::Y
module A::B
# (3)
@@ -153,8 +164,6 @@ the blocks that may be passed to `Class.new` and `Module.new` do not get the
class or module being defined pushed to their nesting. That's one of the
differences between defining classes and modules in one way or another.
-The nesting at any given place can be inspected with `Module.nesting`.
-
### Class and Module Definitions are Constant Assignments
Let's suppose the following snippet creates a class (rather than reopening it):
@@ -236,7 +245,7 @@ end
```
`Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If
-all is good, the constant evaluates to an object that responds to `all`.
+all is good, the constant is evaluated to an object that responds to `all`.
That is why we talk about *constant* autoloading, Rails has the ability to
load constants on the fly.
@@ -330,7 +339,7 @@ checked.
Rails autoloading **does not emulate this algorithm**, but its starting point is
the name of the constant to be autoloaded, and the parent. See more in
-[Qualified References](#qualified-references).
+[Qualified References](#autoloading-algorithms-qualified-references).
Vocabulary
@@ -685,12 +694,12 @@ creates an empty module and assigns it to the `Admin` constant on the fly.
### Generic Procedure
Relative references are reported to be missing in the cref where they were hit,
-and qualified references are reported to be missing in their parent. (See
+and qualified references are reported to be missing in their parent (see
[Resolution Algorithm for Relative
Constants](#resolution-algorithm-for-relative-constants) at the beginning of
this guide for the definition of *cref*, and [Resolution Algorithm for Qualified
Constants](#resolution-algorithm-for-qualified-constants) for the definition of
-*parent*.)
+*parent*).
The procedure to autoload constant `C` in an arbitrary situation is as follows:
@@ -868,8 +877,8 @@ end
```
To resolve `User` Ruby checks `Admin` in the former case, but it does not in
-the latter because it does not belong to the nesting. (See [Nesting](#nesting)
-and [Resolution Algorithms](#resolution-algorithms).)
+the latter because it does not belong to the nesting (see [Nesting](#nesting)
+and [Resolution Algorithms](#resolution-algorithms)).
Unfortunately Rails autoloading does not know the nesting in the spot where the
constant was missing and so it is not able to act as Ruby would. In particular,
@@ -1284,7 +1293,7 @@ c.user # NameError: uninitialized constant C::User
```
because it detects that a parent namespace already has the constant (see [Qualified
-References](#autoloading-algorithms-qualified-references).)
+References](#autoloading-algorithms-qualified-references)).
As with pure Ruby, within the body of a direct descendant of `BasicObject` use
always absolute constant paths:
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index d5477eb72d..0fa20f7ccf 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Caching with Rails: An overview
===============================
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 7567a38aef..78f26ccefa 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
The Rails Command Line
======================
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 622f79a50f..9c0f2ddc8a 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Configuring Rails Applications
==============================
@@ -692,7 +692,7 @@ development:
pool: 5
```
-Prepared Statements are enabled by default on PostgreSQL. You can be disable prepared statements by setting `prepared_statements` to `false`:
+Prepared Statements are enabled by default on PostgreSQL. You can disable prepared statements by setting `prepared_statements` to `false`:
```yaml
production:
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index db3f19f8ac..7381521658 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Contributing to Ruby on Rails
=============================
@@ -26,7 +26,7 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge
### Creating a Bug Report
-If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
+If you've found a problem in Ruby on Rails which is not a security risk, do a search on GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index cef9ac083b..a788dd48ad 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Debugging Rails Applications
============================
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 9eacc3a2fe..989b29956c 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Development Dependencies Install
================================
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 731178787f..6eb558885f 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Getting Started with Engines
============================
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index a8dcd3ee4f..4c02c99cc7 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Form Helpers
============
@@ -275,7 +275,7 @@ There are a few things to note here:
The resulting HTML is:
```html
-<form accept-charset="UTF-8" action="/articles/create" method="post" class="nifty_form">
+<form accept-charset="UTF-8" action="/articles" method="post" class="nifty_form">
<input id="article_title" name="article[title]" type="text" />
<textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea>
<input name="commit" type="submit" value="Create" />
@@ -300,7 +300,7 @@ You can create a similar binding without actually creating `<form>` tags with th
which produces the following output:
```html
-<form accept-charset="UTF-8" action="/people/create" class="new_person" id="new_person" method="post">
+<form accept-charset="UTF-8" action="/people" class="new_person" id="new_person" method="post">
<input id="person_name" name="person[name]" type="text" />
<input id="contact_detail_phone_number" name="contact_detail[phone_number]" type="text" />
</form>
diff --git a/guides/source/generators.md b/guides/source/generators.md
index 05bf07b4c8..14f451cbc9 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Creating and Customizing Rails Generators & Templates
=====================================================
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index d80622ef00..5945d48e98 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Getting Started with Rails
==========================
@@ -1271,8 +1271,8 @@ bottom of the template:
```html+erb
...
-<%= link_to 'Back', articles_path %> |
-<%= link_to 'Edit', edit_article_path(@article) %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
And here's how our app looks so far:
@@ -1485,6 +1485,9 @@ Without this file, the confirmation dialog box wouldn't appear.
![Confirm Dialog](images/getting_started/confirm_dialog.png)
+TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on
+[Working With Javascript in Rails](working_with_javascript_in_rails.html) guide.
+
Congratulations, you can now create, show, list, update and destroy
articles.
@@ -1681,8 +1684,8 @@ So first, we'll wire up the Article show template
</p>
<% end %>
-<%= link_to 'Back', articles_path %> |
-<%= link_to 'Edit', edit_article_path(@article) %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
This adds a form on the `Article` show page that creates a new comment by
@@ -1762,8 +1765,8 @@ add that to the `app/views/articles/show.html.erb`.
</p>
<% end %>
-<%= link_to 'Edit Article', edit_article_path(@article) %> |
-<%= link_to 'Back to Articles', articles_path %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
Now you can add articles and comments to your blog and have them show up in the
@@ -1828,8 +1831,8 @@ following:
</p>
<% end %>
-<%= link_to 'Edit Article', edit_article_path(@article) %> |
-<%= link_to 'Back to Articles', articles_path %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
This will now render the partial in `app/views/comments/_comment.html.erb` once
@@ -1878,8 +1881,8 @@ Then you make the `app/views/articles/show.html.erb` look like the following:
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
-<%= link_to 'Edit Article', edit_article_path(@article) %> |
-<%= link_to 'Back to Articles', articles_path %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
The second render just defines the partial template we want to render,
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index bd6babff41..fbee267975 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Rails Internationalization (I18n) API
=====================================
@@ -530,7 +530,7 @@ Thus the following calls are equivalent:
```ruby
I18n.t 'activerecord.errors.messages.record_invalid'
-I18n.t 'errors.messages.record_invalid', scope: :active_record
+I18n.t 'errors.messages.record_invalid', scope: :activerecord
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
```
@@ -809,7 +809,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co
| validation | with option | message | interpolation |
| ------------ | ------------------------- | ------------------------- | ------------- |
-| confirmation | - | :confirmation | - |
+| confirmation | - | :confirmation | attribute |
| acceptance | - | :accepted | - |
| presence | - | :blank | - |
| absence | - | :present | - |
@@ -829,6 +829,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co
| numericality | :equal_to | :equal_to | count |
| numericality | :less_than | :less_than | count |
| numericality | :less_than_or_equal_to | :less_than_or_equal_to | count |
+| numericality | :other_than | :other_than | count |
| numericality | :only_integer | :not_an_integer | - |
| numericality | :odd | :odd | - |
| numericality | :even | :even | - |
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index a93ceb7fb5..8fbb234698 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
The Rails Initialization Process
================================
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index c3cde49630..69d3f6e86c 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Layouts and Rendering in Rails
==============================
@@ -255,7 +255,7 @@ extension for the layout file.
#### Rendering HTML
-You can send a HTML string back to the browser by using the `:html` option to
+You can send an HTML string back to the browser by using the `:html` option to
`render`:
```ruby
@@ -316,12 +316,13 @@ NOTE: Unless overridden, your response returned from this render option will be
#### Options for `render`
-Calls to the `render` method generally accept four options:
+Calls to the `render` method generally accept five options:
* `:content_type`
* `:layout`
* `:location`
* `:status`
+* `:formats`
##### The `:content_type` Option
@@ -430,6 +431,15 @@ Rails understands both numeric status codes and the corresponding symbols shown
NOTE: If you try to render content along with a non-content status code
(100-199, 204, 205 or 304), it will be dropped from the response.
+##### The `:formats` Option
+
+Rails uses the format specified in request (or `:html` by default). You can change this adding the `:formats` option with a symbol or an array:
+
+```ruby
+render formats: :xml
+render formats: [:json, :xml]
+```
+
#### Finding Layouts
To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions.
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 45cdc549f7..50308f505a 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Maintenance Policy for Ruby on Rails
====================================
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index 44236ad239..1937369776 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Rails Nested Model Forms
========================
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index bd884441ac..10738320ef 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
The Basics of Creating Rails Plugins
====================================
diff --git a/guides/source/profiling.md b/guides/source/profiling.md
index 695b09647f..ce093f78ba 100644
--- a/guides/source/profiling.md
+++ b/guides/source/profiling.md
@@ -1,4 +1,4 @@
-*DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+*DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
A Guide to Profiling Rails Applications
=======================================
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 0db777b9bb..b3e1874048 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Rails Application Templates
===========================
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 561a3d9392..21be74beba 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Rails on Rack
=============
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 893cedeefc..a689e131ff 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Rails Routing from the Outside In
=================================
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index 0ecf8a80df..1323742488 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails Guides Guidelines
===============================
diff --git a/guides/source/security.md b/guides/source/security.md
index e4cc79df55..e486edde31 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails Security Guide
============================
@@ -1029,7 +1029,6 @@ Additional Resources
The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here:
-* The Ruby on Rails security project posts security news regularly: [http://www.rorsecurity.info](http://www.rorsecurity.info)
* Subscribe to the Rails security [mailing list](http://groups.google.com/group/rubyonrails-security)
* [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too)
* A [good security blog](http://ha.ckers.org/blog/) including the [Cross-Site scripting Cheat Sheet](http://ha.ckers.org/xss.html)
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 21b0b37efa..fa55c09c64 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
A Guide to Testing Rails Applications
=====================================
@@ -528,6 +528,19 @@ All of request types have equivalent methods that you can use. In a typical C.R.
NOTE: Functional tests do not verify whether the specified request type is accepted by the action, we're more concerned with the result. Request tests exist for this use case to make your tests more purposeful.
+### Testing XHR (AJAX) requests
+
+`xhr` accepts method (listed in the section above), action name and parameters:
+
+```ruby
+test "ajax request responds with no layout" do
+ xhr :get, :show, id: articles(:first).id
+
+ assert_template :index
+ assert_template layout: nil
+end
+```
+
### The Four Hashes of the Apocalypse
After a request has been made and processed, you will have 4 Hash objects ready for use:
@@ -740,10 +753,8 @@ class ArticlesControllerTest < ActionController::TestCase
# called after every single test
def teardown
- # as we are re-initializing @article before every test
- # setting it to nil here is not essential but I hope
- # you understand how you can use the teardown method
- @article = nil
+ # when controller is using cache it may be a good idea to reset it afterwards
+ Rails.cache.clear
end
test "should show article" do
@@ -769,6 +780,41 @@ end
Similar to other callbacks in Rails, the `setup` and `teardown` methods can also be used by passing a block, lambda, or method name as a symbol to call.
+### Test helpers
+
+To avoid code duplication, you can add your own test helpers.
+Sign in helper can be a good example:
+
+```ruby
+test/test_helper.rb
+
+module SignInHelper
+ def sign_in(user)
+ session[:user_id] = user.id
+ end
+end
+
+class ActionController::TestCase
+ include SignInHelper
+end
+```
+
+```ruby
+require 'test_helper'
+
+class ProfileControllerTest < ActionController::TestCase
+
+ test "should show profile" do
+ # helper is now reusable from any controller test case
+ sign_in users(:david)
+
+ get :show
+ assert_response :success
+ assert_equal users(:david), assigns(:user)
+ end
+end
+```
+
Testing Routes
--------------
@@ -1050,9 +1096,10 @@ require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Send the email, then test that it got queued
- email = UserMailer.create_invite('me@example.com',
- 'friend@example.com', Time.now).deliver_now
- assert_not ActionMailer::Base.deliveries.empty?
+ assert_emails 1 do
+ email = UserMailer.create_invite('me@example.com',
+ 'friend@example.com', Time.now).deliver_now
+ end
# Test the body of the sent email contains what we expect it to
assert_equal ['me@example.com'], email.from
@@ -1154,7 +1201,7 @@ within a model:
```ruby
require 'test_helper'
-class ProductTest < ActiveSupport::TestCase
+class ProductTest < ActiveJob::TestCase
test 'billing job scheduling' do
assert_enqueued_with(job: BillingJob) do
product.charge(account)
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index c0c94475e0..d7fbe575c2 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
A Guide for Upgrading Ruby on Rails
===================================
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 5131f809d7..367abddcfa 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE IN GITHUB, GUIDES ARE PUBLISHED IN http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Working with JavaScript in Rails
================================
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 3e797e03cb..30b86db8bb 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Add the `method_source` gem to the default Gemfile for apps
+
+ *Sean Griffin*
+
* Drop old test locations from `rake stats`
- test/functional
- test/unit
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 2821c8d757..dc3ec4274b 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -6,7 +6,7 @@ require 'rails/source_annotation_extractor'
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
- attr_accessor :allow_concurrency, :asset_host, :assets, :autoflush_log,
+ attr_accessor :allow_concurrency, :asset_host, :autoflush_log,
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
@@ -49,21 +49,6 @@ module Rails
@secret_token = nil
@secret_key_base = nil
@x = Custom.new
-
- @assets = ActiveSupport::OrderedOptions.new
- @assets.enabled = true
- @assets.paths = []
- @assets.precompile = [ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) },
- /(?:\/|\\|\A)application\.(css|js)$/ ]
- @assets.prefix = "/assets"
- @assets.version = '1.0'
- @assets.debug = false
- @assets.compile = true
- @assets.digest = false
- @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/#{Rails.env}/" ]
- @assets.js_compressor = nil
- @assets.css_compressor = nil
- @assets.logger = nil
end
def encoding=(value)
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 63bf8b11ba..5175e31f14 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -171,7 +171,9 @@ module Rails
commands = Array(commands)
dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
- commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+ unless (ext = RbConfig::CONFIG['EXEEXT']).empty?
+ commands = commands.map{|cmd| "#{cmd}#{ext}"}
+ end
full_path_command = nil
found = commands.detect do |cmd|
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index a338f31f15..1290b16872 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -599,12 +599,6 @@ module Rails
end
end
- initializer :append_assets_path, group: :all do |app|
- app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories)
- app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories)
- app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
- end
-
initializer :prepend_helpers_path do |app|
if !isolated? || (app == self)
app.config.helpers_paths.unshift(*paths["app/helpers"].existent)
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index 5e194783ff..c3b8ef1181 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -1,6 +1,6 @@
<p id="notice"><%%= notice %></p>
-<h1>Listing <%= plural_table_name.titleize %></h1>
+<h1><%= plural_table_name.titleize %></h1>
<table>
<thead>
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 3659edcfcd..143673f711 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -36,6 +36,10 @@ group :development, :test do
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
<% end -%>
+
+ # Adds `Method#source` and `Method#comment` to get the source code of a
+ # method from the console
+ gem 'method_source'
<% end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index f9cd5b3483..0cdd2788d0 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -6,9 +6,8 @@
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any styles
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
- * file per style scope.
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
+ * files in this directory. It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
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 c59ffb3491..20ee229e97 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -3,6 +3,7 @@ require File.expand_path('../boot', __FILE__)
<% if include_all_railties? -%>
require 'rails/all'
<% else -%>
+require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 99d7bfb3c9..75666d20c5 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -14,12 +14,6 @@ Rails.application.configure do
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Enable Rack::Cache to put a simple HTTP cache in front of your application
- # Add `rack-cache` to your Gemfile before enabling this.
- # For large-scale production use, consider using a caching reverse proxy like
- # NGINX, varnish or squid.
- # config.action_dispatch.rack_cache = true
-
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
@@ -39,6 +33,9 @@ Rails.application.configure do
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
<%- end -%>
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
+ # config.action_controller.asset_host = 'http://assets.example.com'
+
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
@@ -59,8 +56,9 @@ Rails.application.configure do
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
- # Enable serving of images, stylesheets, and JavaScripts from an asset server.
- # config.action_controller.asset_host = 'http://assets.example.com'
+ # Use a real queuing backend for Active Job (and separate queues per environment)
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}"
<%- unless options.skip_action_mailer? -%>
# Ignore bad email addresses and do not raise email delivery errors.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
new file mode 100644
index 0000000000..ea930f54da
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
@@ -0,0 +1,6 @@
+## Change renderer defaults here.
+#
+# ApplicationController.renderer.defaults.merge!(
+# http_host: 'example.org',
+# https: false
+# )
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index f2110c2c70..94f612c3dd 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -11,6 +11,6 @@ end
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
-# self.include_root_in_json = true
+# self.include_root_in_json = true
# end
<%- end -%>
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 8c3b63c3b4..b8f7845896 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -79,10 +79,15 @@ Available field types:
`rails generate model product supplier:references{polymorphic}:index`
If you require a `password_digest` string column for use with
- has_secure_password, you should specify `password:digest`:
+ has_secure_password, you can specify `password:digest`:
`rails generate model user password:digest`
+ If you require a `token` string column for use with
+ has_secure_token, you can specify `auth_token:token`:
+
+ `rails generate model user auth_token:token`
+
Examples:
`rails generate model account`
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
index f9cd5b3483..0cdd2788d0 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
@@ -6,9 +6,8 @@
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any styles
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
- * file per style scope.
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
+ * files in this directory. It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 2b33beaa2b..8c24d1d56d 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -93,7 +93,7 @@ module Rails
# end
# end
#
- # By default, Rails load generators from your load path. However, if you want to place
+ # By default, Rails loads generators from your load path. However, if you want to place
# your generators at a different location, you can specify in your Railtie a block which
# will load them during normal generators lookup:
#
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 40fd7b77f1..5d270555ed 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -409,6 +409,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_inclusion_of_method_source
+ run_generator
+ assert_file "Gemfile" do |content|
+ assert_gem 'method_source'
+ end
+ end
+
def test_inclusion_of_doc
run_generator
assert_file 'Gemfile', /gem 'sdoc',\s+'~> 0.4.0',\s+group: :doc/
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 17c3f979c0..2c4d9c5995 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -69,7 +69,8 @@ module TestHelpers
def assert_welcome(resp)
assert_equal 200, resp[0]
- assert resp[1]["Content-Type"] = "text/html"
+ assert_match 'text/html', resp[1]["Content-Type"]
+ assert_match 'charset=utf-8', resp[1]["Content-Type"]
assert extract_body(resp).match(/Welcome aboard/)
end