aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md128
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb35
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb7
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb26
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionpack/test/controller/mime_responds_test.rb21
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb3
-rw-r--r--actionpack/test/dispatch/routing_test.rb27
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb1
-rw-r--r--actionpack/test/template/date_helper_test.rb36
-rw-r--r--actionpack/test/template/form_options_helper_test.rb16
-rw-r--r--activemodel/CHANGELOG.md50
-rw-r--r--activerecord/CHANGELOG.md116
-rw-r--r--activerecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb38
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb31
-rw-r--r--activerecord/lib/active_record/core.rb49
-rw-r--r--activerecord/lib/active_record/explain.rb53
-rw-r--r--activerecord/lib/active_record/integration.rb6
-rw-r--r--activerecord/lib/active_record/persistence.rb26
-rw-r--r--activerecord/lib/active_record/querying.rb24
-rw-r--r--activerecord/lib/active_record/railtie.rb22
-rw-r--r--activerecord/lib/active_record/relation.rb12
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb53
-rw-r--r--activerecord/lib/active_record/transactions.rb31
-rw-r--r--activerecord/test/cases/adapter_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb3
-rw-r--r--activerecord/test/cases/associations/eager_test.rb5
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb10
-rw-r--r--activerecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb21
-rw-r--r--activerecord/test/cases/explain_test.rb72
-rw-r--r--activerecord/test/cases/migration/columns_test.rb (renamed from activerecord/test/cases/migration/rename_column_test.rb)62
-rw-r--r--activerecord/test/cases/migration/index_test.rb28
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb12
-rw-r--r--activerecord/test/cases/persistence_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb16
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb35
-rw-r--r--activerecord/test/cases/transactions_test.rb4
-rw-r--r--activerecord/test/models/speedometer.rb2
-rw-r--r--activesupport/CHANGELOG.md169
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb15
-rw-r--r--activesupport/lib/active_support/number_helper.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb4
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb11
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb25
-rw-r--r--activesupport/test/number_helper_test.rb7
-rw-r--r--guides/code/getting_started/config/environments/development.rb4
-rw-r--r--guides/code/getting_started/config/environments/production.rb4
-rw-r--r--guides/source/4_0_release_notes.md2
-rw-r--r--guides/source/active_record_callbacks.md16
-rw-r--r--guides/source/active_record_querying.md39
-rw-r--r--guides/source/configuring.md2
-rw-r--r--guides/source/migrations.md10
-rw-r--r--guides/source/upgrading_ruby_on_rails.md6
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG.md12
-rw-r--r--railties/lib/rails/app_rails_loader.rb24
-rw-r--r--railties/lib/rails/application/configuration.rb6
-rw-r--r--railties/lib/rails/cli.rb3
-rw-r--r--railties/lib/rails/code_statistics.rb68
-rw-r--r--railties/lib/rails/code_statistics_calculator.rb79
-rw-r--r--railties/lib/rails/generators/app_base.rb16
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt6
-rw-r--r--railties/lib/rails/tasks/documentation.rake2
-rw-r--r--railties/test/app_rails_loader_test.rb65
-rw-r--r--railties/test/application/initializers/boot_test.rb20
-rw-r--r--railties/test/code_statistics_calculator_test.rb288
-rw-r--r--railties/test/fixtures/lib/app_builders/empty_builder.rb2
-rw-r--r--railties/test/fixtures/lib/app_builders/simple_builder.rb7
-rw-r--r--railties/test/fixtures/lib/app_builders/tweak_builder.rb7
-rw-r--r--railties/test/fixtures/lib/plugin_builders/empty_builder.rb2
-rw-r--r--railties/test/fixtures/lib/plugin_builders/simple_builder.rb7
-rw-r--r--railties/test/fixtures/lib/plugin_builders/spec_builder.rb19
-rw-r--r--railties/test/fixtures/lib/plugin_builders/tweak_builder.rb7
-rw-r--r--railties/test/generators/app_generator_test.rb34
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb35
-rw-r--r--railties/test/generators/shared_generator_tests.rb58
97 files changed, 1443 insertions, 893 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index b9bff4958f..5be7f34331 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,17 +1,29 @@
## Rails 4.0.0 (unreleased) ##
-* Remove support for parsing XML parameters from request. If you still want to parse XML
- parameters, please install `actionpack-xml_parser' gem.
+* Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser*
- *Prem Sichanugrist*
+* New applications use an encrypted session store by default.
+
+ *Santiago Pastorino*
+
+* Determine the controller#action from only the matched path when using the
+ shorthand syntax. Previously the complete path was used, which led
+ to problems with nesting (scopes and namespaces).
+ Fixes #7554.
-* Fix `time_zone_options_for_select` to call `dup` on the returned TimeZone array.
+ Example:
+
+ # This will route to questions#new.
+ scope ':locale' do
+ get 'questions/new'
+ end
+
+ *Yves Senn*
- Previously if you supplied :priority_zones options to `time_zone_options_for_select`
- the memoized ActiveSupport::TimeZone.all array would be mutated. Calling
- `dup` prevents mutation of the main TimeZones array.
+* Remove support for parsing XML parameters from request. If you still want to parse XML
+ parameters, please install `actionpack-xml_parser' gem.
- *Brian McManus*
+ *Prem Sichanugrist*
* Remove support for parsing YAML parameters from request.
@@ -19,7 +31,7 @@
* Add a message when you have no routes defined to both `rake routes` and
GET "/rails/info/routes" that lets you know you have none defined and links
- to the Rails Guide on the topic.
+ to the Rails guide on the topic.
*Steve Klabnik*
@@ -33,31 +45,30 @@
screen readers by converting both hyphens and underscores to spaces.
Before:
+
image_tag('underscored_file_name.png')
# => <img alt="Underscored_file_name" src="/assets/underscored_file_name.png" />
After:
+
image_tag('underscored_file_name.png')
# => <img alt="Underscored file name" src="/assets/underscored_file_name.png" />
*Nick Cox*
-* We don't support the `:controller` option for route definitions
- with the ruby constant notation. This will now result in an
- `ArgumentError`.
+* We don't support Ruby constant notation in the `:controller` option for route
+ definitions. So, this raises an `ArgumentError` now:
- Example:
- # This raises an ArgumentError:
- resources :posts, :controller => "Admin::Posts"
+ resources :posts, controller: "Admin::Posts" # WRONG
+
+ Use path notation instead:
- # Use directory notation instead:
- resources :posts, :controller => "admin/posts"
+ resources :posts, controller: "admin/posts" # RIGHT
*Yves Senn*
* `assert_template` can be used to verify the locals of partials,
which live inside a directory.
- Fixes #8516.
# Prefixed partials inside directories worked and still work.
assert_template partial: 'directory/_partial', locals: {name: 'John'}
@@ -65,23 +76,25 @@
# This did not work but does now.
assert_template partial: 'directory/partial', locals: {name: 'John'}
+ Fixes #8516.
+
*Yves Senn*
-* Fix `content_tag_for` with array html option.
+* Fix `content_tag_for` with array HTML option.
It would embed array as string instead of joining it like `content_tag` does:
content_tag(:td, class: ["foo", "bar"]){}
- #=> '<td class="foo bar"></td>'
+ # => <td class="foo bar"></td>
Before:
content_tag_for(:td, item, class: ["foo", "bar"])
- #=> '<td class="item [&quot;foo&quot;, &quot;bar&quot;]" id="item_1"></td>'
+ # => <td class="item [&quot;foo&quot;, &quot;bar&quot;]" id="item_1"></td>
After:
content_tag_for(:td, item, class: ["foo", "bar"])
- #=> '<td class="item foo bar" id="item_1"></td>'
+ # => <td class="item foo bar" id="item_1"></td>
*Semyon Perepelitsa*
@@ -101,35 +114,18 @@
*Piotr Sarnacki*
-* Add javascript based routing path matcher to `/rails/info/routes`.
+* Add JavaScript based routing path matcher to `/rails/info/routes`.
Routes can now be filtered by whether or not they match a path.
*Richard Schneeman*
-* Given
-
- params.permit(:name)
-
- `:name` passes if it is a key of `params` whose value is a permitted scalar.
-
- Similarly, given
-
- params.permit(tags: [])
-
- `:tags` passes if it is a key of `params` whose value is an array of
- permitted scalars.
-
- Permitted scalars filtering happens at any level of nesting.
-
- *Xavier Noria*
-
* Change the behavior of route defaults so that explicit defaults are no longer
required where the key is not part of the path. For example:
resources :posts, bucket_type: 'posts'
will be required whenever constructing the url from a hash such as a functional
- test or using url_for directly. However using the explicit form alters the
+ test or using `url_for` directly. However using the explicit form alters the
behavior so it's not required:
resources :projects, defaults: { bucket_type: 'projects' }
@@ -163,7 +159,7 @@
*Colin Burn-Murdoch*
-* Fixed json params parsing regression for non-object JSON content.
+* Fixed JSON params parsing regression for non-object JSON content.
*Dylan Smith*
@@ -201,12 +197,13 @@
* Do not append second slash to `root_url` when using `trailing_slash: true`
Fix #8700
- Example:
- # before
- root_url # => http://test.host//
+ Before:
- # after
- root_url # => http://test.host/
+ root_url(trailing_slash: true) # => http://test.host//
+
+ After:
+
+ root_url(trailing_slash: true) # => http://test.host/
*Yves Senn*
@@ -230,8 +227,8 @@
*Yves Senn*
-* Added `Mime::NullType` class. This allows to use html?, xml?, json?..etc when
- the `format` of `request` is unknown, without raise an exception.
+* Added `Mime::NullType` class. This allows to use `html?`, `xml?`, `json?`, etc.
+ when the format of the request is unknown, without raising an exception.
*Angelo Capilleri*
@@ -256,7 +253,7 @@
*Matt Venables*
-* Prevent raising EOFError on multipart GET request (IE issue). *Adam Stankiewicz*
+* Prevent raising `EOFError` on multipart GET request (IE issue). *Adam Stankiewicz*
* Rename all action callbacks from *_filter to *_action to avoid the misconception that these
callbacks are only suited for transforming or halting the response. With the new style,
@@ -302,7 +299,7 @@
*Stephen Ausman + Fabrizio Regini + Angelo Capilleri*
-* Add filter capability to ActionController logs for redirect locations:
+* Add logging filter capability for redirect URLs:
config.filter_redirect << 'http://please.hide.it/'
@@ -401,23 +398,17 @@
Before:
check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1)
- #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" />
+ # => <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" />
After:
check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1)
- #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" />
+ # => <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" />
Fix #8108.
*Daniel Fox, Grant Hutchins & Trace Wax*
-* `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's
- returned value if any.
- Fix #8086.
-
- *Nikita Afanasenko*
-
* `date_select` helper accepts `with_css_classes: true` to add css classes similar with type
of generated select tags.
@@ -470,10 +461,6 @@
* Failsafe exception returns `text/plain`. *Steve Klabnik*
-* Remove `rack-cache` dependency from Action Pack and declare it on Gemfile
-
- *Guillermo Iguaran*
-
* Rename internal variables on `ActionController::TemplateAssertions` to prevent
naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore.
Fix #7459.
@@ -559,9 +546,7 @@
*Guillermo Iguaran*
* `ActionDispatch::Session::MemCacheStore` now uses `dalli` instead of the deprecated
- `memcache-client` gem. As side effect the autoloading of unloaded classes objects
- saved as values in session isn't supported anymore when mem_cache session store is
- used, this can have an impact in apps only when config.cache_classes is false.
+ `memcache-client` gem.
*Arun Agrawal + Guillermo Iguaran*
@@ -835,9 +820,9 @@
* `assert_generates`, `assert_recognizes`, and `assert_routing` all raise
`Assertion` instead of `RoutingError` *David Chelimsky*
-* URL path parameters with invalid encoding now raise ActionController::BadRequest. *Andrew White*
+* URL path parameters with invalid encoding now raise `ActionController::BadRequest`. *Andrew White*
-* Malformed query and request parameter hashes now raise ActionController::BadRequest. *Andrew White*
+* Malformed query and request parameter hashes now raise `ActionController::BadRequest`. *Andrew White*
* Add `divider` option to `grouped_options_for_select` to generate a separator
`optgroup` automatically, and deprecate `prompt` as third argument, in favor
@@ -864,7 +849,7 @@
*Andrew White*
-* `respond_to` and `respond_with` now raise ActionController::UnknownFormat instead
+* `respond_to` and `respond_with` now raise `ActionController::UnknownFormat` instead
of directly returning head 406. The exception is rescued and converted to 406
in the exception handling middleware. *Steven Soroka*
@@ -894,7 +879,7 @@
* Remove the leading \n added by textarea on `assert_select`. *Santiago Pastorino*
* Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms`
- to `false`. This change breaks remote forms that need to work also without javascript,
+ to `false`. This change breaks remote forms that need to work also without JavaScript,
so if you need such behavior, you can either set it to `true` or explicitly pass
`authenticity_token: true` in form options.
@@ -989,9 +974,6 @@
* `check_box` with `:form` html5 attribute will now replicate the `:form`
attribute to the hidden field as well. *Carlos Antonio da Silva*
-* Turn off verbose mode of rack-cache, we still have X-Rack-Cache to
- check that info. Closes #5245. *Santiago Pastorino*
-
* `label` form helper accepts `for: nil` to not generate the attribute. *Carlos Antonio da Silva*
* Add `:format` option to `number_to_percentage`. *Rodrigo Flores*
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 93568da9ef..834d44f045 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -420,7 +420,7 @@ module ActionController #:nodoc:
end
def response
- @responses[format] || @responses[Mime::ALL]
+ @responses.fetch(format, @responses[Mime::ALL])
end
def negotiate_format(request)
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 17379cf7ac..d275a854fd 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -50,6 +50,10 @@ module ActionController #:nodoc:
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
+ # Holds the class which implements the request forgery protection.
+ config_accessor :forgery_protection_strategy
+ self.forgery_protection_strategy = nil
+
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
config_accessor :allow_forgery_protection
self.allow_forgery_protection = true if allow_forgery_protection.nil?
@@ -82,14 +86,14 @@ module ActionController #:nodoc:
# * <tt>:reset_session</tt> - Resets the session.
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
def protect_from_forgery(options = {})
- include protection_method_module(options[:with] || :null_session)
+ self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
self.request_forgery_protection_token ||= :authenticity_token
prepend_before_action :verify_authenticity_token, options
end
private
- def protection_method_module(name)
+ def protection_method_class(name)
ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
rescue NameError
raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
@@ -97,17 +101,22 @@ module ActionController #:nodoc:
end
module ProtectionMethods
- module NullSession
- protected
+ class NullSession
+ def initialize(controller)
+ @controller = controller
+ end
# This is the method that defines the application behavior when a request is found to be unverified.
def handle_unverified_request
+ request = @controller.request
request.session = NullSessionHash.new(request.env)
request.env['action_dispatch.request.flash_hash'] = nil
request.env['rack.session.options'] = { skip: true }
request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
end
+ protected
+
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
def initialize(env)
super(nil, env)
@@ -135,16 +144,20 @@ module ActionController #:nodoc:
end
end
- module ResetSession
- protected
+ class ResetSession
+ def initialize(controller)
+ @controller = controller
+ end
def handle_unverified_request
- reset_session
+ @controller.reset_session
end
end
- module Exception
- protected
+ class Exception
+ def initialize(controller)
+ @controller = controller
+ end
def handle_unverified_request
raise ActionController::InvalidAuthenticityToken
@@ -153,6 +166,10 @@ module ActionController #:nodoc:
end
protected
+ def handle_unverified_request
+ forgery_protection_strategy.new(self).handle_unverified_request
+ end
+
# The actual before_action that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
unless verified_request?
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 7e720ca6f5..e4dcd3213f 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -191,9 +191,9 @@ module ActionController
#
# +:name+ passes it is a key of +params+ whose associated value is of type
# +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
- # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, or
- # +ActionDispatch::Http::UploadedFile+. Otherwise, the key +:name+ is
- # filtered out.
+ # +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
+ # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
+ # Otherwise, the key +:name+ is filtered out.
#
# You may declare that the parameter should be an array of permitted scalars
# by mapping it to an empty array:
@@ -374,6 +374,7 @@ module ActionController
StringIO,
IO,
ActionDispatch::Http::UploadedFile,
+ Rack::Test::UploadedFile,
]
def permitted_scalar?(value)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 0a41ed0fcf..a8e225d61c 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -50,7 +50,6 @@ module ActionDispatch
class Mapping #:nodoc:
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- SHORTHAND_REGEX = %r{/[\w/]+$}
WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
@@ -111,17 +110,7 @@ module ActionDispatch
@options[:controller] ||= /.+?/
end
- if using_match_shorthand?(path_without_format, @options)
- to_shorthand = @options[:to].blank?
- @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1')
- end
-
- @options.merge!(default_controller_and_action(to_shorthand))
- end
-
- # match "account/overview"
- def using_match_shorthand?(path, options)
- path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
+ @options.merge!(default_controller_and_action)
end
def normalize_format!
@@ -214,7 +203,7 @@ module ActionDispatch
Constraints.new(endpoint, blocks, @set.request_class)
end
- def default_controller_and_action(to_shorthand=nil)
+ def default_controller_and_action
if to.respond_to?(:call)
{ }
else
@@ -227,7 +216,7 @@ module ActionDispatch
controller ||= default_controller
action ||= default_action
- unless controller.is_a?(Regexp) || to_shorthand
+ unless controller.is_a?(Regexp)
controller = [@scope[:module], controller].compact.join("/").presence
end
@@ -1383,6 +1372,11 @@ module ActionDispatch
paths = [path] + rest
end
+ path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
+ if using_match_shorthand?(path_without_format, options)
+ options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
+ end
+
options[:anchor] = true unless options.key?(:anchor)
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
@@ -1393,6 +1387,10 @@ module ActionDispatch
self
end
+ def using_match_shorthand?(path, options)
+ path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$}
+ end
+
def decomposed_match(path, options) # :nodoc:
if on = options.delete(:on)
send(on) { decomposed_match(path, options) }
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index a989966613..d3953c26b7 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -642,6 +642,8 @@ module ActionView
# <time datetime="2010-11-03">Yesterday</time>
# time_tag Date.today, pubdate: true # =>
# <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
+ # time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
+ # <time datetime="2010-W44">November 04, 2010</time>
#
# <%= time_tag Time.now do %>
# <span>Right now</span>
@@ -651,7 +653,7 @@ module ActionView
options = args.extract_options!
format = options.delete(:format) || :long
content = args.first || I18n.l(date_or_time, :format => format)
- datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339
+ datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
end
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
index 34fc23ac1a..c29c1b1eea 100644
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -32,7 +32,7 @@ module ActionView
content_tag(:pre, object, :class => "debug_dump")
rescue Exception # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
- content_tag(:code, object.to_yaml, :class => "debug_dump")
+ content_tag(:code, object.inspect, :class => "debug_dump")
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 49473dd129..377819a80c 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -560,19 +560,19 @@ module ActionView
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
zone_options = "".html_safe
- zones = model.all.dup
+ zones = model.all
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
if priority_zones
if priority_zones.is_a?(Regexp)
- priority_zones = zones.select { |z| z =~ priority_zones }
+ priority_zones = zones.grep(priority_zones)
end
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled')
zone_options.safe_concat "\n"
- zones.reject! { |z| priority_zones.include?(z) }
+ zones = zones - priority_zones
end
zone_options.safe_concat options_for_select(convert_zones[zones], selected)
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 86d9f94067..1adc8225f1 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -33,7 +33,7 @@ module ActionView
# (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token
# by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
# This is helpful when you're fragment-caching the form. Remote forms get the
- # authenticity from the <tt>meta</tt> tag, so embedding is unnecessary unless you
+ # authenticity token from the <tt>meta</tt> tag, so embedding is unnecessary unless you
# support browsers without JavaScript.
# * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index ed013e2185..a9c62899b5 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -80,6 +80,13 @@ class RespondToController < ActionController::Base
respond_to(:html, :xml)
end
+ def using_defaults_with_all
+ respond_to do |type|
+ type.html
+ type.all{ render text: "ALL" }
+ end
+ end
+
def made_for_content_type
respond_to do |type|
type.rss { render :text => "RSS" }
@@ -301,6 +308,20 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "<p>Hello world!</p>\n", @response.body
end
+ def test_using_defaults_with_all
+ @request.accept = "*/*"
+ get :using_defaults_with_all
+ assert_equal "HTML!", @response.body.strip
+
+ @request.accept = "text/html"
+ get :using_defaults_with_all
+ assert_equal "HTML!", @response.body.strip
+
+ @request.accept = "application/json"
+ get :using_defaults_with_all
+ assert_equal "ALL", @response.body
+ end
+
def test_using_defaults_with_type_list
@request.accept = "*/*"
get :using_defaults_with_type_list
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index aadb142660..437da43d9b 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -32,7 +32,8 @@ class ParametersPermitTest < ActiveSupport::TestCase
values += [0, 1.0, 2**128, BigDecimal.new(1)]
values += [true, false]
values += [Date.today, Time.now, DateTime.now]
- values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__)]
+ values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__),
+ Rack::Test::UploadedFile.new(__FILE__)]
values.each do |value|
params = ActionController::Parameters.new(id: value)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 143733254b..37ad9ddb6b 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1146,6 +1146,33 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'api/products#list', @response.body
end
+ def test_match_shorthand_inside_scope_with_variables_with_controller
+ draw do
+ scope ':locale' do
+ match 'questions/new', via: [:get]
+ end
+ end
+
+ get '/de/questions/new'
+ assert_equal 'questions#new', @response.body
+ assert_equal 'de', @request.params[:locale]
+ end
+
+ def test_match_shorthand_inside_nested_namespaces_and_scopes_with_controller
+ draw do
+ namespace :api do
+ namespace :v3 do
+ scope ':locale' do
+ get "products/list"
+ end
+ end
+ end
+ end
+
+ get '/api/v3/en/products/list'
+ assert_equal 'api/v3/products#list', @response.body
+ end
+
def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper
draw do
resources :replies do
diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb b/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb
new file mode 100644
index 0000000000..9f1f855269
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb
@@ -0,0 +1 @@
+HTML!
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index f11adefad8..242b56a1fd 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -1510,42 +1510,42 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on")
end
-
+
def test_date_select_with_selected
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
-
+
expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
-
+
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7" selected="selected">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
expected << "</select>\n"
-
+
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
-
+
expected << "</select>\n"
-
+
assert_dom_equal expected, date_select("post", "written_on", :selected => Date.new(2004, 07, 10))
end
def test_date_select_with_selected_nil
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
-
+
expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1"/>' + "\n"
-
+
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
expected << %{<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
expected << "</select>\n"
-
+
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
expected << %{<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
-
+
expected << "</select>\n"
-
+
assert_dom_equal expected, date_select("post", "written_on", include_blank: true, discard_year: true, selected: nil)
end
@@ -2296,7 +2296,7 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true, selected: nil)
end
-
+
def test_datetime_select_defaults_to_time_zone_now_when_config_time_zone_is_set
# The love zone is UTC+0
mytz = Class.new(ActiveSupport::TimeZone) {
@@ -3160,14 +3160,14 @@ class DateHelperTest < ActionView::TestCase
end
def test_time_tag_with_date
- date = Date.today
- expected = "<time datetime=\"#{date.rfc3339}\">#{I18n.l(date, :format => :long)}</time>"
+ date = Date.new(2013, 2, 20)
+ expected = '<time datetime="2013-02-20">February 20, 2013</time>'
assert_equal expected, time_tag(date)
end
def test_time_tag_with_time
- time = Time.now
- expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :long)}</time>"
+ time = Time.new(2013, 2, 20, 0, 0, 0, '+00:00')
+ expected = '<time datetime="2013-02-20T00:00:00+00:00">February 20, 2013 00:00</time>'
assert_equal expected, time_tag(time)
end
@@ -3184,8 +3184,8 @@ class DateHelperTest < ActionView::TestCase
end
def test_time_tag_with_different_format
- time = Time.now
- expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :short)}</time>"
+ time = Time.new(2013, 2, 20, 0, 0, 0, '+00:00')
+ expected = '<time datetime="2013-02-20T00:00:00+00:00">20 Feb 00:00</time>'
assert_equal expected, time_tag(time, :format => :short)
end
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 757b05dbc1..04cdd068c8 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -21,10 +21,10 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def setup
- @fake_timezones = %w(A B C D E).inject([]) do |zones, id|
+ @fake_timezones = %w(A B C D E).map do |id|
tz = TZInfo::Timezone.loaded_zones[id] = stub(:name => id, :to_s => id)
ActiveSupport::TimeZone.stubs(:[]).with(id).returns(tz)
- zones << tz
+ tz
end
ActiveSupport::TimeZone.stubs(:all).returns(@fake_timezones)
end
@@ -351,7 +351,7 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
- def test_time_zone_options_no_parms
+ def test_time_zone_options_no_params
opts = time_zone_options_for_select
assert_dom_equal "<option value=\"A\">A</option>\n" +
"<option value=\"B\">B</option>\n" +
@@ -416,11 +416,11 @@ class FormOptionsHelperTest < ActionView::TestCase
"<option value=\"D\">D</option>",
opts
end
-
+
def test_time_zone_options_with_priority_zones_does_not_mutate_time_zones
original_zones = ActiveSupport::TimeZone.all.dup
zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
- opts = time_zone_options_for_select( nil, zones )
+ time_zone_options_for_select(nil, zones)
assert_equal original_zones, ActiveSupport::TimeZone.all
end
@@ -1086,11 +1086,13 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select_with_priority_zones_as_regexp
@firm = Firm.new("D")
+
+ priority_zones = /A|D/
@fake_timezones.each_with_index do |tz, i|
- tz.stubs(:=~).returns(i.zero? || i == 3)
+ priority_zones.stubs(:===).with(tz).returns(i.zero? || i == 3)
end
- html = time_zone_select("firm", "time_zone", /A|D/)
+ html = time_zone_select("firm", "time_zone", priority_zones)
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"D\" selected=\"selected\">D</option>" +
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 09e6ede064..416922cb89 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -19,7 +19,7 @@
*Roberto Vasquez Angel*
-* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`
+* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`.
*Renato Mascarenhas*
@@ -27,9 +27,11 @@
*Rafael Mendonça França*
-* Specify type of singular association during serialization *Steve Klabnik*
+* Specify type of singular association during serialization.
-* Fixed length validator to correctly handle nil values. Fixes #7180.
+ *Steve Klabnik*
+
+* Fixed length validator to correctly handle `nil`. Fixes #7180.
*Michal Zima*
@@ -53,24 +55,27 @@
*Guillermo Iguaran*
-* Due to a change in builder, nil values and empty strings now generates
+* Due to a change in builder, `nil` and empty strings now generate
closed tags, so instead of this:
<pseudonyms nil=\"true\"></pseudonyms>
- It generates this:
+ it generates this:
<pseudonyms nil=\"true\"/>
*Carlos Antonio da Silva*
-* Changed inclusion and exclusion validators to accept a symbol for `:in` option.
+* Inclusion/exclusion validators accept a method name passed as a symbol to the
+ `:in` option.
- This allows to use dynamic inclusion/exclusion values using methods, besides the current lambda/proc support.
+ This allows to use dynamic inclusion/exclusion values using methods, besides
+ the current lambda/proc support.
*Gabriel Sobrinho*
-* `AM::Validation#validates` ability to pass custom exception to `:strict` option.
+* `ActiveModel::Validation#validates` ability to pass custom exception to the
+ `:strict` option.
*Bogdan Gusiev*
@@ -81,7 +86,7 @@
*Anthony Alberto*
-* Changed `AM::Serializers::JSON.include_root_in_json' default value to false.
+* Changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to false.
Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578.
class User < ActiveRecord::Base; end
@@ -108,16 +113,31 @@
*Francesco Rodriguez*
-* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell*
+* Passing false hash values to `validates` will no longer enable the corresponding validators.
+
+ *Steve Purcell*
+
+* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute`.
+
+ *Brian Cardarella*
+
+* Added `ActiveModel::Model`, a mixin to make Ruby objects work with AP out of box.
-* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*
+ *Guillermo Iguaran*
+
+* `AM::Errors#to_json`: support `:full_messages` parameter.
+
+ *Bogdan Gusiev*
-* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran*
+* Trim down Active Model API by removing `valid?` and `errors.full_messages`.
-* `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev*
+ *José Valim*
-* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim*
+* When `^` or `$` are used in the regular expression provided to `validates_format_of`
+ and the `:multiline` option is not set to true, an exception will be raised. This is
+ to prevent security vulnerabilities when using `validates_format_of`. The problem is
+ described in detail in the Rails security guide.
-* When `^` or `$` are used in the regular expression provided to `validates_format_of` and the :multiline option is not set to true, an exception will be raised. This is to prevent security vulnerabilities when using `validates_format_of`. The problem is described in detail in the Rails security guide *Jan Berdajs + Egor Homakov*
+ *Jan Berdajs + Egor Homakov*
Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a888b79391..ecdf829823 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,18 +1,89 @@
## Rails 4.0.0 (unreleased) ##
+* Fixing issue #8345. Now throwing an error when one attempts to touch a
+ new object that has not yet been persisted. For instance:
+
+ Example:
+
+ ball = Ball.new
+ ball.touch :updated_at # => raises error
+
+ It is not until the ball object has been persisted that it can be touched.
+ This follows the behavior of update_column.
+
+ *John Wang*
+
+* Preloading ordered `has_many :through` associations no longer applies
+ invalid ordering to the `:through` association.
+ Fixes #8663.
+
+ *Yves Senn*
+
+* The auto explain feature has been removed. This feature was
+ activated by configuring `config.active_record.auto_explain_threshold_in_seconds`.
+ The configuration option was deprecated and has no more effect.
+
+ You can still use `ActiveRecord::Relation#explain` to see the EXPLAIN output for
+ any given relation.
+
+ *Yves Senn*
+
+* The `:on` option for `after_commit` and `after_rollback` now
+ accepts an Array of actions.
+ Fixes #988.
+
+ Example:
+
+ after_commit :update_cache on: [:create, :update]
+
+ *Yves Senn*
+
+* Rename related indexes on `rename_table` and `rename_column`. This
+ does not affect indexes with custom names.
+
+ *Yves Senn*
+
+* Prevent the creation of indices with too long names, which cause
+ internal operations to fail (sqlite3 adapter only). The method
+ `allowed_index_name_length` defines the length limit enforced by
+ rails. It's value defaults to `index_name_length` but can vary per adapter.
+ Fixes #8264.
+
+ *Yves Senn*
+
+* Fixing issue #776.
+
+ Memory bloat in transactions is handled by having the transaction hold only
+ the AR objects which it absolutely needs to know about. These are the AR
+ objects with callbacks (they need to be updated as soon as something in the
+ transaction occurs).
+
+ All other AR objects can be updated lazily by keeping a reference to a
+ TransactionState object. If an AR object gets inside a transaction, then
+ the transaction will add its TransactionState to the AR object. When the
+ user makes a call to some attribute on an AR object (which has no
+ callbacks) associated with a transaction, the AR object will call the
+ sync_with_transaction_state method and make sure it is up to date with the
+ transaction. After it has synced with the transaction state, the AR object
+ will return the attribute that was requested.
+
+ Most of the logic in the changes are used to handle multiple transactions,
+ in which case the AR object has to recursively follow parent pointers of
+ TransactionState objects.
+
+ *John Wang*
+
* Descriptive error message when the necessary AR adapter gem was not found.
- Fix #7313
+ Fixes #7313.
*Yves Senn*
-* ActiveRecord now raises an error when blank arguments are passed to query
- methods for which blank arguments do not make sense. This also occurs for
- nil-like objects in arguments.
+* Active Record now raises an error when blank arguments are passed to query
+ methods for which blank arguments do not make sense.
Example:
- Post.limit() # => raises error
- Post.include([]) # => raises error
+ Post.includes() # => raises error
*John Wang*
@@ -110,8 +181,8 @@
*Justin George*
-* The `DATABASE_URL` environment variable now converts ints, floats, and
- the strings true and false to Ruby types. For example, SQLite requires
+* The database adpters now converts the options passed thought `DATABASE_URL`
+ environment variable to the proper Ruby types before using. For example, SQLite requires
that the timeout value is an integer, and PostgreSQL requires that the
prepared_statements option is a boolean. These now work as expected:
@@ -120,7 +191,7 @@
DATABASE_URL=sqlite3://localhost/test_db?timeout=500
DATABASE_URL=postgresql://localhost/test_db?prepared_statements=false
- *Aaron Stone*
+ *Aaron Stone + Rafael Mendonça França*
* `Relation#merge` now only overwrites where values on the LHS of the
merge. Consider:
@@ -177,7 +248,7 @@
*Lilibeth De La Cruz*
* When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`.
- Fix #6865.
+ Fixes #6865.
Example:
@@ -210,7 +281,7 @@
*John Wang*
* Collection associations `#empty?` always respects builded records.
- Fix #8879.
+ Fixes #8879.
Example:
@@ -299,18 +370,13 @@
*Yves Senn*
* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control
- the format of the timestamp value in the cache key.
- This allows users to improve the precision of the cache key.
+ the format of the timestamp value in the cache key. Defaults to `:nsec`.
Fixes #8195.
*Rafael Mendonça França*
-* Add `:nsec` date format. This can be used to improve the precision of cache key.
-
- *Jamie Gaskins*
-
* Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters
- in the `variables: <hash>` parameter in `database.yml`. The key-value pairs of this
+ in the `variables: <hash>` parameter in `config/database.yml`. The key-value pairs of this
hash will be sent in a `SET key = value` query on new database connections. See also:
http://dev.mysql.com/doc/refman/5.0/en/set-statement.html
http://www.postgresql.org/docs/8.3/static/sql-set.html
@@ -333,7 +399,7 @@
to the update query.
class User < ActiveRecord::Base
- default_scope where(active: true)
+ default_scope -> { where(active: true) }
end
user = User.first
@@ -470,11 +536,6 @@
*kennyj*
-* Added `#none!` method for mutating `ActiveRecord::Relation` objects to a NullRelation.
- It acts like `#none` but modifies relation in place.
-
- *Juanjo Bazán*
-
* Fix bug where `update_columns` and `update_column` would not let you update the primary key column.
*Henrik Nyh*
@@ -1252,13 +1313,6 @@
*Joshua Wood*
-* Added bang methods for mutating `ActiveRecord::Relation` objects.
- For example, while `foo.where(:bar)` will return a new object
- leaving `foo` unchanged, `foo.where!(:bar)` will mutate the foo
- object
-
- *Jon Leighton*
-
* Added `#find_by` and `#find_by!` to mirror the functionality
provided by dynamic finders in a way that allows dynamic input more
easily:
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 06bdabfced..513d1012ba 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -965,8 +965,8 @@ module ActiveRecord
# For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they
# cause the records in the join table to be removed.
#
- # For +has_many+, <tt>destroy</tt> will always call the <tt>destroy</tt> method of the
- # record(s) being removed so that callbacks are run. However <tt>delete</tt> will either
+ # For +has_many+, <tt>destroy</tt> and <tt>destory_all</tt> will always call the <tt>destroy</tt> method of the
+ # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
# The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index b0b1c13b0d..c4b50ab306 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -47,12 +47,12 @@ module ActiveRecord
through_scope.where! reflection.foreign_type => options[:source_type]
else
unless reflection_scope.where_values.empty?
- through_scope.includes_values = reflection_scope.values[:includes] || options[:source]
+ through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
through_scope.where_values = reflection_scope.values[:where]
end
- through_scope.order! reflection_scope.values[:order]
through_scope.references! reflection_scope.values[:references]
+ through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading?
end
through_scope
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 310f1b6e75..3e454b713a 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -8,27 +8,32 @@ module ActiveRecord
# Returns this record's primary key value wrapped in an Array if one is
# available.
def to_key
+ sync_with_transaction_state
key = self.id
[key] if key
end
# Returns the primary key value.
def id
+ sync_with_transaction_state
read_attribute(self.class.primary_key)
end
# Sets the primary key value.
def id=(value)
+ sync_with_transaction_state
write_attribute(self.class.primary_key, value) if self.class.primary_key
end
# Queries the primary key value.
def id?
+ sync_with_transaction_state
query_attribute(self.class.primary_key)
end
# Returns the primary key value before type cast.
def id_before_type_cast
+ sync_with_transaction_state
read_attribute_before_type_cast(self.class.primary_key)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 30ccb8f0a4..2859fb31e8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -17,6 +17,15 @@ module ActiveRecord
64
end
+ # Returns the maximum allowed length for an index name. This
+ # limit is enforced by rails and Is less than or equal to
+ # <tt>index_name_length</tt>. The gap between
+ # <tt>index_name_length</tt> is to allow internal rails
+ # opreations to use prefixes in temporary opreations.
+ def allowed_index_name_length
+ index_name_length
+ end
+
# Returns the maximum length of an index name.
def index_name_length
64
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 cdc8433185..9bae880024 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -192,6 +192,14 @@ module ActiveRecord
# Set to true to drop the table before creating it.
# Defaults to false.
#
+ # Note that +create_join_table+ does not create any indices by default; you can use
+ # its block form to do so yourself:
+ #
+ # create_join_table :products, :categories do |t|
+ # t.index :products
+ # t.index :categories
+ # end
+ #
# ====== Add a backend specific option to the generated SQL (MySQL)
# create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
# generates:
@@ -647,10 +655,11 @@ module ActiveRecord
index_name = index_name(table_name, column: column_names)
if Hash === options # legacy support, since this param was a string
- options.assert_valid_keys(:unique, :order, :name, :where, :length)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal)
index_type = options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
@@ -665,10 +674,11 @@ module ActiveRecord
end
index_type = options
+ max_index_length = allowed_index_name_length
end
- if index_name.length > index_name_length
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
+ if index_name.length > max_index_length
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
end
if index_name_exists?(table_name, index_name, false)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
@@ -694,6 +704,28 @@ module ActiveRecord
column_names.map {|column_name| quote_column_name(column_name) }
end
+ def rename_table_indexes(table_name, new_name)
+ indexes(new_name).each do |index|
+ generated_index_name = index_name(table_name, column: index.columns)
+ if generated_index_name == index.name
+ rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
+ end
+ end
+ end
+
+ def rename_column_indexes(table_name, column_name, new_column_name)
+ column_name, new_column_name = column_name.to_s, new_column_name.to_s
+ indexes(table_name).each do |index|
+ next unless index.columns.include?(new_column_name)
+ old_columns = index.columns.dup
+ old_columns[old_columns.index(new_column_name)] = column_name
+ generated_index_name = index_name(table_name, column: old_columns)
+ if generated_index_name == index.name
+ rename_index table_name, generated_index_name, index_name(table_name, column: index.columns)
+ end
+ end
+ end
+
private
def table_definition
TableDefinition.new(self)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 3ecef96b10..73c80a3220 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -5,7 +5,7 @@ module ActiveRecord
def initialize(connection)
@connection = connection
- @state = TransactionState.new
+ @state = TransactionState.new
end
def state
@@ -14,11 +14,13 @@ module ActiveRecord
end
class TransactionState
+ attr_accessor :parent
VALID_STATES = Set.new([:committed, :rolledback, nil])
def initialize(state = nil)
@state = state
+ @parent = nil
end
def committed?
@@ -116,7 +118,11 @@ module ActiveRecord
end
def add_record(record)
- records << record
+ if record.has_transactional_callbacks?
+ records << record
+ else
+ record.set_transaction_state(@state)
+ end
end
def rollback_records
@@ -188,8 +194,9 @@ module ActiveRecord
end
def perform_commit
+ @state.set_state(:committed)
+ @state.parent = parent.state
connection.release_savepoint
- records.each { |r| parent.add_record(r) }
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 67d4d505f7..ff9de712bc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -61,12 +61,30 @@ module ActiveRecord
include MonitorMixin
include ColumnDumper
+ SIMPLE_INT = /\A\d+\z/
+
define_callbacks :checkout, :checkin
attr_accessor :visitor, :pool
attr_reader :schema_cache, :last_use, :in_use, :logger
alias :in_use? :in_use
+ def self.type_cast_config_to_integer(config)
+ if config =~ SIMPLE_INT
+ config.to_i
+ else
+ config
+ end
+ end
+
+ def self.type_cast_config_to_boolean(config)
+ if config == "false"
+ false
+ else
+ config
+ end
+ end
+
def initialize(connection, logger = nil, pool = nil) #:nodoc:
super()
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 e07dbc7da9..5480204511 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -31,7 +31,7 @@ module ActiveRecord
return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
super
end
-
+
def blob_or_text_column?
sql_type =~ /blob/i || type == :text
end
@@ -140,7 +140,7 @@ module ActiveRecord
@connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {}
- if config.fetch(:prepared_statements) { true }
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::MySQL.new self
else
@visitor = BindSubstitution.new self
@@ -468,6 +468,7 @@ module ActiveRecord
# rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
+ rename_table_indexes(table_name, new_name)
end
def add_column(table_name, column_name, type, options = {})
@@ -495,6 +496,7 @@ module ActiveRecord
def rename_column(table_name, column_name, new_column_name) #:nodoc:
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
+ rename_column_indexes(table_name, column_name, new_column_name)
end
# Maps logical Rails types to MySQL-specific data types.
@@ -579,7 +581,7 @@ module ActiveRecord
end
def strict_mode?
- @config.fetch(:strict, true)
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
protected
@@ -720,7 +722,7 @@ module ActiveRecord
# Increase timeout so the server doesn't disconnect us.
wait_timeout = @config[:wait_timeout]
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
- variables[:wait_timeout] = wait_timeout
+ variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout)
# Make MySQL reject illegal values rather than truncating or blanking them, see
# http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 5b60185f53..2c683fc3ac 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -65,10 +65,6 @@ module ActiveRecord
ConnectionSpecification.new(spec, adapter_method)
end
- # For DATABASE_URL, accept a limited concept of ints and floats
- SIMPLE_INT = /\A\d+\z/
- SIMPLE_FLOAT = /\A\d+\.\d+\z/
-
def self.connection_url_to_hash(url) # :nodoc:
config = URI.parse url
adapter = config.scheme
@@ -89,28 +85,11 @@ module ActiveRecord
if config.query
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
- options.each { |key, value| options[key] = type_cast_value(value) }
-
spec.merge!(options)
end
spec
end
-
- def type_cast_value(value)
- case value
- when SIMPLE_INT
- value.to_i
- when SIMPLE_FLOAT
- value.to_f
- when 'true'
- true
- when 'false'
- false
- else
- value
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 631f646f58..7544c2a783 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -126,7 +126,7 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super
@statements = StatementPool.new(@connection,
- config.fetch(:statement_limit) { 1000 })
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
@client_encoding = nil
connect
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 73ca2c8e61..3bc61c5e0c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -321,14 +321,16 @@ module ActiveRecord
#
# Example:
# rename_table('octopuses', 'octopi')
- def rename_table(name, new_name)
+ def rename_table(table_name, new_name)
clear_cache!
- execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
pk, seq = pk_and_sequence_for(new_name)
- if seq == "#{name}_#{pk}_seq"
+ if seq == "#{table_name}_#{pk}_seq"
new_seq = "#{new_name}_#{pk}_seq"
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
end
+
+ rename_table_indexes(table_name, new_name)
end
# Adds a new column to the named table.
@@ -370,6 +372,7 @@ module ActiveRecord
def rename_column(table_name, column_name, new_column_name)
clear_cache!
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
+ rename_column_indexes(table_name, column_name, new_column_name)
end
def remove_index!(table_name, index_name) #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 0818760b11..2bb2557efd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -478,7 +478,7 @@ module ActiveRecord
def initialize(connection, logger, connection_parameters, config)
super(connection, logger)
- if config.fetch(:prepared_statements) { true }
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::PostgreSQL.new self
else
@visitor = BindSubstitution.new self
@@ -492,7 +492,7 @@ module ActiveRecord
connect
@statements = StatementPool.new @connection,
- config.fetch(:statement_limit) { 1000 }
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
if postgresql_version < 80200
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
@@ -500,7 +500,7 @@ module ActiveRecord
initialize_type_map
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
- @use_insert_returning = @config.key?(:insert_returning) ? @config[:insert_returning] : true
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
end
# Clears the prepared statements cache.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 91444950be..981c4c96a0 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -26,7 +26,7 @@ module ActiveRecord
:results_as_hash => true
)
- db.busy_timeout(config[:timeout]) if config[:timeout]
+ db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
end
@@ -107,10 +107,10 @@ module ActiveRecord
@active = nil
@statements = StatementPool.new(@connection,
- config.fetch(:statement_limit) { 1000 })
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
@config = config
- if config.fetch(:prepared_statements) { true }
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::SQLite.new self
else
@visitor = BindSubstitution.new self
@@ -187,6 +187,13 @@ module ActiveRecord
true
end
+ # Returns 62. SQLite supports index names up to 64
+ # characters. The rest is used by rails internally to perform
+ # temporary rename operations
+ def allowed_index_name_length
+ index_name_length - 2
+ end
+
def native_database_types #:nodoc:
{
:primary_key => default_primary_key_type,
@@ -428,8 +435,9 @@ module ActiveRecord
#
# Example:
# rename_table('octopuses', 'octopi')
- def rename_table(name, new_name)
- exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
+ def rename_table(table_name, new_name)
+ exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
+ rename_table_indexes(table_name, new_name)
end
# See: http://www.sqlite.org/lang_altertable.html
@@ -488,6 +496,7 @@ module ActiveRecord
raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
end
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
+ rename_column_indexes(table_name, column_name, new_column_name)
end
protected
@@ -502,7 +511,7 @@ module ActiveRecord
end
def alter_table(table_name, options = {}) #:nodoc:
- altered_table_name = "altered_#{table_name}"
+ altered_table_name = "a#{table_name}"
caller = lambda {|definition| yield definition if block_given?}
transaction do
@@ -546,10 +555,10 @@ module ActiveRecord
def copy_table_indexes(from, to, rename = {}) #:nodoc:
indexes(from).each do |index|
name = index.name
- if to == "altered_#{from}"
- name = "temp_#{name}"
- elsif from == "altered_#{to}"
- name = name[5..-1]
+ if to == "a#{from}"
+ name = "t#{name}"
+ elsif from == "a#{to}"
+ name = name[1..-1]
end
to_column_names = columns(to).map { |c| c.name }
@@ -559,7 +568,7 @@ module ActiveRecord
unless columns.empty?
# index name can't be the same
- opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_") }
+ opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
opts[:unique] = true if index.unique
add_index(to, columns, opts)
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 63a1197a56..6510433056 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -347,8 +347,54 @@ module ActiveRecord
Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access
end
+ def set_transaction_state(state) # :nodoc:
+ @transaction_state = state
+ end
+
+ def has_transactional_callbacks? # :nodoc:
+ !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_create_callbacks.empty?
+ end
+
private
+ # Updates the attributes on this particular ActiveRecord object so that
+ # if it is associated with a transaction, then the state of the AR object
+ # will be updated to reflect the current state of the transaction
+ #
+ # The @transaction_state variable stores the states of the associated
+ # transaction. This relies on the fact that a transaction can only be in
+ # one rollback or commit (otherwise a list of states would be required)
+ # Each AR object inside of a transaction carries that transaction's
+ # TransactionState.
+ #
+ # This method checks to see if the ActiveRecord object's state reflects
+ # the TransactionState, and rolls back or commits the ActiveRecord object
+ # as appropriate.
+ #
+ # Since ActiveRecord objects can be inside multiple transactions, this
+ # method recursively goes through the parent of the TransactionState and
+ # checks if the ActiveRecord object reflects the state of the object.
+ def sync_with_transaction_state
+ update_attributes_from_transaction_state(@transaction_state, 0)
+ end
+
+ def update_attributes_from_transaction_state(transaction_state, depth)
+ if transaction_state && !has_transactional_callbacks?
+ unless @reflects_state[depth]
+ if transaction_state.committed?
+ committed!
+ elsif transaction_state.rolledback?
+ rolledback!
+ end
+ @reflects_state[depth] = true
+ end
+
+ if transaction_state.parent && !@reflects_state[depth+1]
+ update_attributes_from_transaction_state(transaction_state.parent, depth+1)
+ end
+ end
+ end
+
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
# of the array, and then rescues from the possible NoMethodError. If those elements are
# ActiveRecord::Base's, then this triggers the various method_missing's that we have,
@@ -376,7 +422,8 @@ module ActiveRecord
@new_record = true
@txn = nil
@_start_transaction_state = {}
- @transaction = nil
+ @transaction_state = nil
+ @reflects_state = [false]
end
end
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 70683eb731..b2a9a54af1 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -2,43 +2,7 @@ require 'active_support/lazy_load_hooks'
module ActiveRecord
module Explain
- def self.extended(base)
- base.mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
- end
-
- # If the database adapter supports explain and auto explain is enabled,
- # this method triggers EXPLAIN logging for the queries triggered by the
- # block if it takes more than the threshold as a whole. That is, the
- # threshold is not checked against each individual query, but against the
- # duration of the entire block. This approach is convenient for relations.
-
- #
- # The available_queries_for_explain thread variable collects the queries
- # to be explained. If the value is nil, it means queries are not being
- # currently collected. A false value indicates collecting is turned
- # off. Otherwise it is an array of queries.
- def logging_query_plan # :nodoc:
- return yield unless logger
-
- threshold = auto_explain_threshold_in_seconds
- current = Thread.current
- if connection.supports_explain? && threshold && current[:available_queries_for_explain].nil?
- begin
- queries = current[:available_queries_for_explain] = []
- start = Time.now
- result = yield
- logger.warn(exec_explain(queries)) if Time.now - start > threshold
- result
- ensure
- current[:available_queries_for_explain] = nil
- end
- else
- yield
- end
- end
-
- # Relation#explain needs to be able to collect the queries regardless of
- # whether auto explain is enabled. This method serves that purpose.
+ # Relation#explain needs to be able to collect the queries.
def collecting_queries_for_explain # :nodoc:
current = Thread.current
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
@@ -68,20 +32,5 @@ module ActiveRecord
end
str
end
-
- # Silences automatic EXPLAIN logging for the duration of the block.
- #
- # This has high priority, no EXPLAINs will be run even if downwards
- # the threshold is set to 0.
- #
- # As the name of the method suggests this only applies to automatic
- # EXPLAINs, manual calls to <tt>ActiveRecord::Relation#explain</tt> run.
- def silence_auto_explain
- current = Thread.current
- original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
- yield
- ensure
- current[:available_queries_for_explain] = original
- end
end
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 7f877a6471..32d35f0ec1 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -5,8 +5,10 @@ module ActiveRecord
included do
##
# :singleton-method:
- # Indicates the format used to generate the timestamp format in the cache key.
- # This is +:number+, by default.
+ # Indicates the format used to generate the timestamp in the cache key.
+ # Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
+ #
+ # This is +:nsec+, by default.
class_attribute :cache_timestamp_format, :instance_writer => false
self.cache_timestamp_format = :nsec
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 803cae7115..347f023793 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -69,11 +69,13 @@ module ActiveRecord
# Returns true if this object hasn't been saved yet -- that is, a record
# for the object doesn't exist in the data store yet; otherwise, returns false.
def new_record?
+ sync_with_transaction_state
@new_record
end
# Returns true if this object has been destroyed, otherwise returns false.
def destroyed?
+ sync_with_transaction_state
@destroyed
end
@@ -365,7 +367,16 @@ module ActiveRecord
#
# # triggers @brake.car.touch and @brake.car.corporation.touch
# @brake.touch
+ #
+ # Note that +touch+ must be used on a persisted object, or else an
+ # ActiveRecordError will be thrown. For example:
+ #
+ # ball = Ball.new
+ # ball.touch(:updated_at) # => raises ActiveRecordError
+ #
def touch(name = nil)
+ raise ActiveRecordError, "can not touch on a new record object" unless persisted?
+
attributes = timestamp_attributes_for_update_in_model
attributes << name if name
@@ -418,13 +429,22 @@ module ActiveRecord
# Returns the number of affected rows.
def update_record(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
-
if attributes_with_values.empty?
0
else
klass = self.class
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
- klass.connection.update stmt
+ column_hash = klass.connection.schema_cache.columns_hash klass.table_name
+ db_columns_with_values = attributes_with_values.map { |attr,value|
+ real_column = column_hash[attr.name]
+ [real_column, value]
+ }
+ bind_attrs = attributes_with_values.dup
+ bind_attrs.keys.each_with_index do |column, i|
+ real_column = db_columns_with_values[i].first
+ bind_attrs[column] = klass.connection.substitute_at(real_column, i)
+ end
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(bind_attrs)
+ klass.connection.update stmt, 'SQL', db_columns_with_values
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index f08b9c614d..e04a3d0976 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -33,18 +33,16 @@ module ActiveRecord
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
# # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
def find_by_sql(sql, binds = [])
- logging_query_plan do
- result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
- column_types = {}
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
+ column_types = {}
- if result_set.respond_to? :column_types
- column_types = result_set.column_types
- else
- ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
- end
-
- result_set.map { |record| instantiate(record, column_types) }
+ if result_set.respond_to? :column_types
+ column_types = result_set.column_types
+ else
+ ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
end
+
+ result_set.map { |record| instantiate(record, column_types) }
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
@@ -57,10 +55,8 @@ module ActiveRecord
#
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
def count_by_sql(sql)
- logging_query_plan do
- sql = sanitize_conditions(sql)
- connection.select_value(sql, "#{name} Count").to_i
- end
+ sql = sanitize_conditions(sql)
+ connection.select_value(sql, "#{name} Count").to_i
end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a979be6999..64eac3aca7 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -112,7 +112,18 @@ module ActiveRecord
`config/application.rb` file and any `mass_assignment_sanitizer` options
from your `config/environments/*.rb` files.
- See http://guides.rubyonrails.org/security.html#mass-assignment for more information
+ See http://guides.rubyonrails.org/security.html#mass-assignment for more information.
+ EOF
+ end
+
+ unless app.config.active_record.delete(:auto_explain_threshold_in_seconds).nil?
+ ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, []
+ The Active Record auto explain feature has been removed.
+
+ To disable this message remove the `active_record.auto_explain_threshold_in_seconds`
+ option from the `config/environments/*.rb` config file.
+
+ See http://guides.rubyonrails.org/4_0_release_notes.html for more information.
EOF
end
@@ -124,7 +135,7 @@ module ActiveRecord
To disable this message remove the `observers` option from your
`config/application.rb` or from your initializers.
- See http://guides.rubyonrails.org/4_0_release_notes.html for more information
+ See http://guides.rubyonrails.org/4_0_release_notes.html for more information.
EOF
end
ensure
@@ -146,13 +157,6 @@ module ActiveRecord
end
end
- initializer "active_record.validate_explain_support" do |app|
- if app.config.active_record[:auto_explain_threshold_in_seconds] &&
- !ActiveRecord::Base.connection.supports_explain?
- warn "auto_explain_threshold_in_seconds is set but will be ignored because your adapter does not support this feature. Please unset the configuration to avoid this warning."
- end
- end
-
# Expose database runtime to controller for logging.
initializer "active_record.log_runtime" do |app|
require "active_record/railties/controller_runtime"
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 0053530f73..bc50802c4a 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -444,17 +444,7 @@ module ActiveRecord
#
# Post.where(published: true).load # => #<ActiveRecord::Relation>
def load
- unless loaded?
- # We monitor here the entire execution rather than individual SELECTs
- # because from the point of view of the user fetching the records of a
- # relation is a single unit of work. You want to know if this call takes
- # too long, not if the individual queries take too long.
- #
- # It could be the case that none of the queries involved surpass the
- # threshold, and at the same time the sum of them all does. The user
- # should get a query plan logged in that case.
- logging_query_plan { exec_queries }
- end
+ exec_queries unless loaded?
self
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 615309964c..00a506c3a7 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -12,7 +12,7 @@ module ActiveRecord
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
- :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
+ :connection, :columns_hash, :to => :klass
module ClassSpecificRelation
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7ddaea1bb0..14520381c9 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -58,7 +58,7 @@ module ActiveRecord
# order. The order will depend on the database implementation.
# If an order is supplied it will be respected.
#
- # Person.take # returns an object fetched by SELECT * FROM people
+ # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
# Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
# Person.where(["name LIKE '%?'", name]).take
def take(limit = nil)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 85534608ac..225677085f 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -11,11 +11,11 @@ module ActiveRecord
@scope = scope
end
- # Returns a new relation expressing WHERE + NOT condition
- # according to the conditions in the arguments.
+ # Returns a new relation expressing WHERE + NOT condition according to
+ # the conditions in the arguments.
#
- # #not accepts conditions in one of these formats: String, Array, Hash.
- # See #where for more details on each format.
+ # +not+ accepts conditions as a string, array, or hash. See #where for
+ # more details on each format.
#
# User.where.not("name = 'Jon'")
# # SELECT * FROM users WHERE NOT (name = 'Jon')
@@ -31,6 +31,10 @@ module ActiveRecord
#
# User.where.not(name: %w(Ko1 Nobu))
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
+ #
+ # User.where.not(name: "Jon", role: "admin")
+ # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
+ #
def not(opts, *rest)
where_value = @scope.send(:build_where, opts, rest).map do |rel|
case rel
@@ -108,7 +112,7 @@ module ActiveRecord
#
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
def includes(*args)
- check_empty_arguments("includes", *args)
+ check_if_method_has_arguments!("includes", args)
spawn.includes!(*args)
end
@@ -126,7 +130,7 @@ module ActiveRecord
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
# "users"."id"
def eager_load(*args)
- check_empty_arguments("eager_load", *args)
+ check_if_method_has_arguments!("eager_load", args)
spawn.eager_load!(*args)
end
@@ -140,7 +144,7 @@ module ActiveRecord
# User.preload(:posts)
# => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
def preload(*args)
- check_empty_arguments("preload", *args)
+ check_if_method_has_arguments!("preload", args)
spawn.preload!(*args)
end
@@ -158,7 +162,7 @@ module ActiveRecord
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
# # => Query now knows the string references posts, so adds a JOIN
def references(*args)
- check_empty_arguments("references", *args)
+ check_if_method_has_arguments!("references", args)
spawn.references!(*args)
end
@@ -238,7 +242,7 @@ module ActiveRecord
# User.group('name AS grouped_name, age')
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
def group(*args)
- check_empty_arguments("group", *args)
+ check_if_method_has_arguments!("group", args)
spawn.group!(*args)
end
@@ -269,7 +273,7 @@ module ActiveRecord
# User.order(:name, email: :desc)
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
def order(*args)
- check_empty_arguments("order", *args)
+ check_if_method_has_arguments!("order", args)
spawn.order!(*args)
end
@@ -295,7 +299,7 @@ module ActiveRecord
#
# generates a query with 'ORDER BY name ASC, id ASC'.
def reorder(*args)
- check_empty_arguments("reorder", *args)
+ check_if_method_has_arguments!("reorder", args)
spawn.reorder!(*args)
end
@@ -318,8 +322,8 @@ module ActiveRecord
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
# => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
def joins(*args)
- check_empty_arguments("joins", *args)
- spawn.joins!(*args.flatten)
+ check_if_method_has_arguments!("joins", args)
+ spawn.joins!(*args.compact.flatten)
end
def joins!(*args) # :nodoc:
@@ -465,8 +469,6 @@ module ActiveRecord
end
end
- # #where! is identical to #where, except that instead of returning a new relation, it adds
- # the condition to the existing relation.
def where!(opts = :chain, *rest) # :nodoc:
if opts == :chain
WhereChain.new(self)
@@ -483,7 +485,7 @@ module ActiveRecord
#
# Order.having('SUM(price) > 30').group('user_id')
def having(opts, *rest)
- check_empty_arguments("having", opts)
+ opts.blank? ? self : spawn.having!(opts, *rest)
spawn.having!(opts, *rest)
end
@@ -632,7 +634,6 @@ module ActiveRecord
spawn.from!(value, subquery_name)
end
- # Like #from, but modifies relation in place.
def from!(value, subquery_name = nil) # :nodoc:
self.from_value = [value, subquery_name]
self
@@ -921,7 +922,23 @@ module ActiveRecord
end
end
- def check_empty_arguments(method_name, *args)
+ # Checks to make sure that the arguments are not blank. Note that if some
+ # blank-like object were initially passed into the query method, then this
+ # method will not raise an error.
+ #
+ # Example:
+ #
+ # Post.references() # => raises an error
+ # Post.references([]) # => does not raise an error
+ #
+ # This particular method should be called with a method_name and the args
+ # passed into that method as an input. For example:
+ #
+ # def references(*args)
+ # check_if_method_has_arguments!("references", args)
+ # ...
+ # end
+ def check_if_method_has_arguments!(method_name, args)
if args.blank?
raise ArgumentError, "The method .#{method_name}() must contain arguments."
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 4b7a388dc7..33718ef0e9 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -218,9 +218,8 @@ module ActiveRecord
# after_commit :do_bar, on: :update
# after_commit :do_baz, on: :destroy
#
- # Also, to have the callback fired on create and update, but not on destroy:
- #
- # after_commit :do_zoo, if: :persisted?
+ # after_commit :do_foo_bar, :on [:create, :update]
+ # after_commit :do_bar_baz, :on [:update, :destroy]
#
# Note that transactional fixtures do not play well with this feature. Please
# use the +test_after_commit+ gem to have these hooks fired in tests.
@@ -244,12 +243,14 @@ module ActiveRecord
if options.is_a?(Hash) && options[:on]
assert_valid_transaction_action(options[:on])
options[:if] = Array(options[:if])
- options[:if] << "transaction_include_action?(:#{options[:on]})"
+ fire_on = Array(options[:on]).map(&:to_sym)
+ options[:if] << "transaction_include_any_action?(#{fire_on})"
end
end
- def assert_valid_transaction_action(action)
- unless ACTIONS.include?(action.to_sym)
+ def assert_valid_transaction_action(actions)
+ actions = Array(actions)
+ if (actions - ACTIONS).any?
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
end
end
@@ -378,14 +379,16 @@ module ActiveRecord
end
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
- def transaction_include_action?(action) #:nodoc:
- case action
- when :create
- transaction_record_state(:new_record)
- when :destroy
- destroyed?
- when :update
- !(transaction_record_state(:new_record) || destroyed?)
+ def transaction_include_any_action?(actions) #:nodoc:
+ actions.any? do |action|
+ case action
+ when :create
+ transaction_record_state(:new_record)
+ when :destroy
+ destroyed?
+ when :update
+ !(transaction_record_state(:new_record) || destroyed?)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index f9149c1819..0af7cbf74f 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "models/book"
module ActiveRecord
class AdapterTest < ActiveRecord::TestCase
@@ -6,6 +7,19 @@ module ActiveRecord
@connection = ActiveRecord::Base.connection
end
+ ##
+ # PostgreSQL does not support null bytes in strings
+ unless current_adapter?(:PostgreSQLAdapter)
+ def test_update_prepared_statement
+ b = Book.create(name: "my \x00 book")
+ b.reload
+ assert_equal "my \x00 book", b.name
+ b.update_attributes(name: "my other \x00 book")
+ b.reload
+ assert_equal "my other \x00 book", b.name
+ end
+ end
+
def test_tables
tables = @connection.tables
assert tables.include?("accounts")
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index b67d70ede7..b965983fec 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -19,6 +19,9 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_connect_with_url
run_without_connection do |orig|
ar_config = ARTest.connection_config['arunit']
+
+ skip "This test doesn't work with custom socket location" if ar_config['socket']
+
url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}"
Klass.establish_connection(url)
assert_equal ar_config['database'], Klass.connection.current_database
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 3bf9125013..46f3c38ac5 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -73,6 +73,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_has_many_through_with_order
+ authors = Author.includes(:favorite_authors).to_a
+ assert_no_queries { authors.map(&:favorite_authors) }
+ end
+
def test_with_two_tables_in_from_without_getting_double_quoted
posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a
assert_equal 2, posts.first.comments.size
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index fd6d531645..1ddd380f23 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -20,6 +20,8 @@ require 'models/car'
require 'models/bulb'
require 'models/engine'
require 'models/categorization'
+require 'models/minivan'
+require 'models/speedometer'
class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase
class Invoice < ActiveRecord::Base
@@ -1747,4 +1749,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
david.posts_with_special_categorizations = []
assert_equal [], david.posts_with_special_categorizations
end
+
+ test "does not duplicate associations when used with natural primary keys" do
+ speedometer = Speedometer.create!(id: '4')
+ speedometer.minivans.create!(minivan_id: 'a-van-red' ,name: 'a van', color: 'red')
+
+ assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}"
+ assert_equal 1, speedometer.reload.minivans.to_a.size
+ end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index fbc66540d6..af1845c937 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1447,6 +1447,13 @@ class BasicsTest < ActiveRecord::TestCase
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
+ def test_touch_should_raise_error_on_a_new_object
+ company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals")
+ assert_raises(ActiveRecord::ActiveRecordError) do
+ company.touch :updated_at
+ end
+ end
+
def test_cache_key_format_is_precise_enough
dev = Developer.first
key = dev.cache_key
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 0a04117795..c8dfc3244b 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -43,27 +43,6 @@ module ActiveRecord
encoding: "utf8" }, spec)
end
- def test_url_query_numeric
- spec = resolve 'abstract://foo:123?encoding=utf8&int=500&float=10.9'
- assert_equal({
- adapter: "abstract",
- port: 123,
- int: 500,
- float: 10.9,
- host: "foo",
- encoding: "utf8" }, spec)
- end
-
- def test_url_query_boolean
- spec = resolve 'abstract://foo:123?true=true&false=false'
- assert_equal({
- adapter: "abstract",
- port: 123,
- true: true,
- false: false,
- host: "foo" }, spec)
- end
-
def test_encoded_password
password = 'am@z1ng_p@ssw0rd#!'
encoded_password = URI.encode_www_form_component(password)
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index aa2a6d7509..b1d276f9eb 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -14,46 +14,9 @@ if ActiveRecord::Base.connection.supports_explain?
base.connection
end
- def test_logging_query_plan_with_logger
- base.logger.expects(:warn).with do |message|
- message.starts_with?('EXPLAIN for:')
- end
-
- with_threshold(0) do
- Car.where(:name => 'honda').to_a
- end
- end
-
- def test_logging_query_plan_without_logger
- original = base.logger
- base.logger = nil
-
- class << base.logger
- def warn; raise "Should not be called" end
- end
-
- with_threshold(0) do
- car = Car.where(:name => 'honda').first
- assert_equal 'honda', car.name
- end
- ensure
- base.logger = original
- end
-
- def test_collect_queries_for_explain
- base.auto_explain_threshold_in_seconds = nil
- queries = Thread.current[:available_queries_for_explain] = []
-
- with_threshold(0) do
- Car.where(:name => 'honda').to_a
- end
-
- sql, binds = queries[0]
- assert_match "SELECT", sql
- assert_match "honda", sql
- assert_equal [], binds
- ensure
- Thread.current[:available_queries_for_explain] = nil
+ def test_relation_explain
+ message = Car.where(:name => 'honda').explain
+ assert_match(/^EXPLAIN for:/, message)
end
def test_collecting_queries_for_explain
@@ -68,16 +31,6 @@ if ActiveRecord::Base.connection.supports_explain?
assert_equal [cars(:honda)], result
end
- def test_logging_query_plan_when_counting_by_sql
- base.logger.expects(:warn).with do |message|
- message.starts_with?('EXPLAIN for:')
- end
-
- with_threshold(0) do
- Car.count_by_sql "SELECT COUNT(*) FROM cars WHERE name = 'honda'"
- end
- end
-
def test_exec_explain_with_no_binds
sqls = %w(foo bar)
binds = [[], []]
@@ -113,25 +66,8 @@ if ActiveRecord::Base.connection.supports_explain?
base.logger.expects(:warn).never
- with_threshold(0) do
- Car.where(:name => 'honda').to_a
- end
- end
-
- def test_silence_auto_explain
- base.expects(:collecting_sqls_for_explain).never
- base.logger.expects(:warn).never
- base.silence_auto_explain do
- with_threshold(0) { Car.all.to_a }
- end
+ Car.where(:name => 'honda').to_a
end
- def with_threshold(threshold)
- current_threshold = base.auto_explain_threshold_in_seconds
- base.auto_explain_threshold_in_seconds = threshold
- yield
- ensure
- base.auto_explain_threshold_in_seconds = current_threshold
- end
end
end
diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/columns_test.rb
index 8f6918d06a..e52809f0f8 100644
--- a/activerecord/test/cases/migration/rename_column_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -2,7 +2,7 @@ require "cases/migration/helper"
module ActiveRecord
class Migration
- class RenameColumnTest < ActiveRecord::TestCase
+ class ColumnsTest < ActiveRecord::TestCase
include ActiveRecord::Migration::TestHelper
self.use_transactional_fixtures = false
@@ -86,8 +86,37 @@ module ActiveRecord
assert_equal 1, connection.indexes('test_models').size
rename_column "test_models", "hat_name", "name"
- # FIXME: should we rename the index if it's name was autogenerated by rails?
- assert_equal ['index_test_models_on_hat_name'], connection.indexes('test_models').map(&:name)
+
+ assert_equal ['index_test_models_on_name'], connection.indexes('test_models').map(&:name)
+ end
+
+ def test_rename_column_with_multi_column_index
+ add_column "test_models", :hat_size, :integer
+ add_column "test_models", :hat_style, :string, limit: 100
+ add_index "test_models", ["hat_style", "hat_size"], unique: true
+
+ rename_column "test_models", "hat_size", 'size'
+ if current_adapter? :OracleAdapter
+ assert_equal ['i_test_models_hat_style_size'], connection.indexes('test_models').map(&:name)
+ else
+ assert_equal ['index_test_models_on_hat_style_and_size'], connection.indexes('test_models').map(&:name)
+ end
+
+ rename_column "test_models", "hat_style", 'style'
+ if current_adapter? :OracleAdapter
+ assert_equal ['i_test_models_style_size'], connection.indexes('test_models').map(&:name)
+ else
+ assert_equal ['index_test_models_on_style_and_size'], connection.indexes('test_models').map(&:name)
+ end
+ end
+
+ def test_rename_column_does_not_rename_custom_named_index
+ add_column "test_models", :hat_name, :string
+ add_index :test_models, :hat_name, :name => 'idx_hat_name'
+
+ assert_equal 1, connection.indexes('test_models').size
+ rename_column "test_models", "hat_name", "name"
+ assert_equal ['idx_hat_name'], connection.indexes('test_models').map(&:name)
end
def test_remove_column_with_index
@@ -107,7 +136,7 @@ module ActiveRecord
assert_equal 1, connection.indexes('test_models').size
remove_column("test_models", "hat_size")
- # Every database and/or database adapter has their own behavior
+ # Every database and/or database adapter has their own behavior
# if it drops the multi-column index when any of the indexed columns dropped by remove_column.
if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
assert_equal [], connection.indexes('test_models').map(&:name)
@@ -197,6 +226,17 @@ module ActiveRecord
assert_equal ['test_models_categories_idx'], connection.indexes('test_models').map(&:name)
end
+ def test_change_column_with_long_index_name
+ table_name_prefix = 'test_models_'
+ long_index_name = table_name_prefix + ('x' * (connection.allowed_index_name_length - table_name_prefix.length))
+ add_column "test_models", "category", :string
+ add_index :test_models, :category, name: long_index_name
+
+ change_column "test_models", "category", :string, null: false, default: 'article'
+
+ assert_equal [long_index_name], connection.indexes('test_models').map(&:name)
+ end
+
def test_change_column_default
add_column "test_models", "first_name", :string
connection.change_column_default "test_models", "first_name", "Tester"
@@ -213,6 +253,20 @@ module ActiveRecord
def test_remove_column_no_second_parameter_raises_exception
assert_raise(ArgumentError) { connection.remove_column("funny") }
end
+
+ def test_removing_and_renaming_column_preserves_custom_primary_key
+ connection.create_table "my_table", primary_key: "my_table_id", force: true do |t|
+ t.integer "col_one"
+ t.string "col_two", limit: 128, null: false
+ end
+
+ remove_column("my_table", "col_two")
+ rename_column("my_table", "col_one", "col_three")
+
+ assert_equal 'my_table_id', connection.primary_key('my_table')
+ ensure
+ connection.drop_table(:my_table) rescue nil
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index a41f2c10f0..0e375af6e8 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -55,19 +55,31 @@ module ActiveRecord
assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") }
end
- def test_add_index_name_length_limit
- good_index_name = 'x' * connection.index_name_length
+ def test_add_index_works_with_long_index_names
+ connection.add_index(table_name, "foo", name: good_index_name)
+
+ assert connection.index_name_exists?(table_name, good_index_name, false)
+ connection.remove_index(table_name, name: good_index_name)
+ end
+
+ def test_add_index_does_not_accept_too_long_index_names
too_long_index_name = good_index_name + 'x'
- assert_raises(ArgumentError) {
- connection.add_index(table_name, "foo", :name => too_long_index_name)
+ e = assert_raises(ArgumentError) {
+ connection.add_index(table_name, "foo", name: too_long_index_name)
}
+ assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message)
assert_not connection.index_name_exists?(table_name, too_long_index_name, false)
connection.add_index(table_name, "foo", :name => good_index_name)
+ end
+
+ def test_internal_index_with_name_matching_database_limit
+ good_index_name = 'x' * connection.index_name_length
+ connection.add_index(table_name, "foo", name: good_index_name, internal: true)
assert connection.index_name_exists?(table_name, good_index_name, false)
- connection.remove_index(table_name, :name => good_index_name)
+ connection.remove_index(table_name, name: good_index_name)
end
def test_index_symbol_names
@@ -196,6 +208,12 @@ module ActiveRecord
connection.remove_index("testings", "last_name")
assert !connection.index_exists?("testings", "last_name")
end
+
+ private
+ def good_index_name
+ 'x' * connection.allowed_index_name_length
+ end
+
end
end
end
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 21901bec3c..22dbd7c38b 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -63,7 +63,17 @@ module ActiveRecord
connection.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
- assert connection.indexes(:octopi).first.columns.include?("url")
+ index = connection.indexes(:octopi).first
+ assert index.columns.include?("url")
+ assert_equal 'index_octopi_on_url', index.name
+ end
+
+ def test_rename_table_does_not_rename_custom_named_index
+ add_index :test_models, :url, name: 'special_url_idx'
+
+ rename_table :test_models, :octopi
+
+ assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name)
end
def test_rename_table_for_postgresql_should_also_rename_default_sequence
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 08dbf19e7b..8156f99037 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -400,6 +400,8 @@ class PersistencesTest < ActiveRecord::TestCase
end
def test_string_ids
+ # FIXME: Fix this failing test
+ skip "Failing test. We need this fixed before 4.0.0"
mv = Minivan.where(:minivan_id => 1234).first_or_initialize
assert mv.new_record?
assert_equal '1234', mv.minivan_id
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 379c0c0758..8298d7534c 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -321,6 +321,22 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 1, person_with_reader_and_post.size
end
+ def test_no_arguments_to_query_methods_raise_errors
+ assert_raises(ArgumentError) { Topic.references() }
+ assert_raises(ArgumentError) { Topic.includes() }
+ assert_raises(ArgumentError) { Topic.preload() }
+ assert_raises(ArgumentError) { Topic.group() }
+ assert_raises(ArgumentError) { Topic.reorder() }
+ end
+
+ def test_blank_like_arguments_to_query_methods_dont_raise_errors
+ assert_nothing_raised { Topic.references([]) }
+ assert_nothing_raised { Topic.includes([]) }
+ assert_nothing_raised { Topic.preload([]) }
+ assert_nothing_raised { Topic.group([]) }
+ assert_nothing_raised { Topic.reorder([]) }
+ end
+
def test_scoped_responds_to_delegated_methods
relation = Topic.all
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index fd5651b4e0..eb4ffd4498 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -312,3 +312,38 @@ class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase
assert_equal true, topic.record_updated
end
end
+
+class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class TopicWithCallbacksOnMultipleActions < ActiveRecord::Base
+ self.table_name = :topics
+
+ after_commit(on: [:create, :destroy]) { |record| record.history << :create_and_destroy }
+ after_commit(on: [:create, :update]) { |record| record.history << :create_and_update }
+ after_commit(on: [:update, :destroy]) { |record| record.history << :update_and_destroy }
+
+ def clear_history
+ @history = []
+ end
+
+ def history
+ @history ||= []
+ end
+ end
+
+ def test_after_commit_on_multiple_actions
+ topic = TopicWithCallbacksOnMultipleActions.new
+ topic.save
+ assert_equal [:create_and_update, :create_and_destroy], topic.history
+
+ topic.clear_history
+ topic.approved = true
+ topic.save
+ assert_equal [:update_and_destroy, :create_and_update], topic.history
+
+ topic.clear_history
+ topic.destroy
+ assert_equal [:update_and_destroy, :create_and_destroy], topic.history
+ end
+end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 546737b398..6d66342fa5 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -460,7 +460,7 @@ class TransactionTest < ActiveRecord::TestCase
assert !transaction.state.committed?
transaction.perform_rollback
-
+
assert transaction.state.rolledback?
assert !transaction.state.committed?
end
@@ -474,7 +474,7 @@ class TransactionTest < ActiveRecord::TestCase
assert !transaction.state.committed?
transaction.perform_commit
-
+
assert !transaction.state.rolledback?
assert transaction.state.committed?
end
diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb
index 0a7d38d8ec..497c3aba9a 100644
--- a/activerecord/test/models/speedometer.rb
+++ b/activerecord/test/models/speedometer.rb
@@ -1,4 +1,6 @@
class Speedometer < ActiveRecord::Base
self.primary_key = :speedometer_id
belongs_to :dashboard
+
+ has_many :minivans
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 5f7559b5a6..b07ab749a3 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,13 +1,54 @@
## Rails 4.0.0 (unreleased) ##
-* ActiveSupport::Gzip.compress allows two optional arguments for compression
+* Prevent `DateTime#change` from truncating the second fraction, when seconds
+ do not need to be changed.
+
+ *Chris Baynes*
+
+* Added `ActiveSupport::TimeWithZone#to_r` for `Time#at` compatibility.
+
+ Before this change:
+
+ Time.zone = 'Tokyo'
+ time = Time.zone.now
+ time == Time.at(time) # => false
+
+ After the change:
+
+ Time.zone = 'Tokyo'
+ time = Time.zone.now
+ time == Time.at(time) # => true
+
+ *stopdropandrew*
+
+* `ActiveSupport::NumberHelper#number_to_human` returns the number unaltered when
+ the given units hash does not contain the needed key, e.g. when the number provided
+ is less than the largest key provided.
+ Fixes #9269.
+
+ Examples:
+
+ number_to_human(123, units: {}) # => 123
+ number_to_human(123, units: { thousand: 'k' }) # => 123
+
+ *Michael Hoffman*
+
+* Added `beginning_of_minute` support to core ext calculations for `Time` and `DateTime`.
+
+ *Gagan Awhad*
+
+* Add `:nsec` date format.
+
+ *Jamie Gaskins*
+
+* `ActiveSupport::Gzip.compress` allows two optional arguments for compression
level and strategy.
*Beyond*
* Modify `TimeWithZone#as_json` to include 3 decimal places of sub-second accuracy
by default, which is optional as per the ISO8601 spec, but extremely useful. Also
- the default behaviour of Date#toJSON() in recent versions of Chrome, Safari and
+ the default behaviour of `Date#toJSON()` in recent versions of Chrome, Safari and
Firefox.
*James Harton*
@@ -20,7 +61,7 @@
*Andrew White*
* Extract `ActiveSupport::Testing::Performance` into https://github.com/rails/rails-perftest
- You can add the gem to your Gemfile to keep using performance tests.
+ You can add the gem to your `Gemfile` to keep using performance tests.
gem 'rails-perftest'
@@ -47,7 +88,7 @@
*Kelly Stannard*
* It's now possible to compare `Date`, `DateTime`, `Time` and `TimeWithZone`
- with `Infinity`. This allows to create date/time ranges with one infinite bound.
+ with `Float::INFINITY`. This allows to create date/time ranges with one infinite bound.
Example:
range = Range.new(Date.today, Float::INFINITY)
@@ -80,13 +121,13 @@
* Remove surrogate unicode character encoding from `ActiveSupport::JSON.encode`
The encoding scheme was broken for unicode characters outside the basic multilingual plane;
- since json is assumed to be `UTF-8`, and we already force the encoding to `UTF-8`,
+ since json is assumed to be UTF-8, and we already force the encoding to UTF-8,
simply pass through the un-encoded characters.
*Brett Carter*
* Deprecate `Time.time_with_date_fallback`, `Time.utc_time` and `Time.local_time`.
- These methods were added to handle the limited range of Ruby's native Time
+ These methods were added to handle the limited range of Ruby's native `Time`
implementation. Those limitations no longer apply so we are deprecating them in 4.0
and they will be removed in 4.1.
@@ -94,16 +135,17 @@
* Deprecate `Date#to_time_in_current_zone` and add `Date#in_time_zone`. *Andrew White*
-* Add `String#in_time_zone` method to convert a string to an ActiveSupport::TimeWithZone. *Andrew White*
+* Add `String#in_time_zone` method to convert a string to an `ActiveSupport::TimeWithZone`. *Andrew White*
* Deprecate `ActiveSupport::BasicObject` in favor of `ActiveSupport::ProxyObject`.
- This class is used for proxy classes. It avoids confusion with Ruby's BasicObject
+ This class is used for proxy classes. It avoids confusion with Ruby's `BasicObject`
class.
*Francesco Rodriguez*
-* Patched Marshal#load to work with constant autoloading.
- Fixes autoloading with cache stores that relay on Marshal(MemCacheStore and FileStore). [fixes #8167]
+* Patched `Marshal#load` to work with constant autoloading. Fixes autoloading
+ with cache stores that rely on `Marshal` (`MemCacheStore` and `FileStore`).
+ Fixes #8167.
*Uriel Katz*
@@ -118,9 +160,9 @@
*Olek Janiszewski*
-* No longer proxy ActiveSupport::Multibyte#class. *Steve Klabnik*
+* No longer proxy `ActiveSupport::Multibyte#class`. *Steve Klabnik*
-* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. *Carlos Antonio da Silva*
+* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from minitest instead. *Carlos Antonio da Silva*
* `XmlMini.with_backend` now may be safely used with threads:
@@ -135,18 +177,17 @@
*Nikita Afanasenko*
-* Dependencies no longer trigger Kernel#autoload in remove_constant [fixes #8213]. *Xavier Noria*
+* Dependencies no longer trigger `Kernel#autoload` in `remove_constant`. Fixes #8213. *Xavier Noria*
-* Simplify mocha integration and remove monkey-patches, bumping mocha to 0.13.0. *James Mead*
+* Simplify `mocha` integration and remove monkey-patches, bumping `mocha` to 0.13.0. *James Mead*
-* `#as_json` isolates options when encoding a hash.
- Fix #8182
+* `#as_json` isolates options when encoding a hash. Fixes #8182.
*Yves Senn*
-* Deprecate Hash#diff in favor of MiniTest's #diff. *Steve Klabnik*
+* Deprecate `Hash#diff` in favor of minitest's #diff. *Steve Klabnik*
-* Kernel#capture can catch output from subprocesses *Dmitry Vorotilin*
+* `Kernel#capture` can catch output from subprocesses. *Dmitry Vorotilin*
* `to_xml` conversions now use builder's `tag!` method instead of explicit invocation of `method_missing`.
@@ -154,27 +195,30 @@
* Fixed timezone mapping of the Solomon Islands. *Steve Klabnik*
-* Make callstack attribute optional in
- ActiveSupport::Deprecation::Reporting methods `warn` and `deprecation_warning`
+* Make callstack attribute optional in `ActiveSupport::Deprecation::Reporting`
+ methods `warn` and `deprecation_warning`.
*Alexey Gaziev*
-* Implement HashWithIndifferentAccess#replace so key? works correctly. *David Graham*
+* Implement `HashWithIndifferentAccess#replace` so `key?` works correctly. *David Graham*
-* Handle the possible Permission Denied errors atomic.rb might trigger due to its chown and chmod calls. *Daniele Sluijters*
+* Handle the possible permission denied errors `atomic.rb` might trigger due to its `chown`
+ and `chmod` calls.
-* Hash#extract! returns only those keys that present in the receiver.
+ *Daniele Sluijters*
+
+* `Hash#extract!` returns only those keys that present in the receiver.
{a: 1, b: 2}.extract!(:a, :x) # => {:a => 1}
*Mikhail Dieterle*
-* Hash#extract! returns the same subclass, that the receiver is. I.e.
- HashWithIndifferentAccess#extract! returns HashWithIndifferentAccess instance.
+* `Hash#extract!` returns the same subclass, that the receiver is. I.e.
+ `HashWithIndifferentAccess#extract!` returns a `HashWithIndifferentAccess` instance.
*Mikhail Dieterle*
-* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. *Brian Durand*
+* Optimize `ActiveSupport::Cache::Entry` to reduce memory and processing overhead. *Brian Durand*
* Tests tag the Rails log with the current test class and test case:
@@ -183,7 +227,7 @@
*Jeremy Kemper*
-* Add `logger.push_tags` and `.pop_tags` to complement logger.tagged:
+* Add `logger.push_tags` and `.pop_tags` to complement `logger.tagged`:
class Job
def before
@@ -219,6 +263,7 @@
class User
include ActiveSupport::Configurable
+
config_accessor :hair_colors do
[:brown, :black, :blonde, :red]
end
@@ -228,7 +273,7 @@
*Larry Lv*
-* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of
+* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of
thread safety. It will be removed without replacement in Rails 4.1.
*Steve Klabnik*
@@ -238,14 +283,14 @@
*Pranas Kiziela*
-* ActiveSupport::Deprecation is now a class. It is possible to create an instance
+* `ActiveSupport::Deprecation` is now a class. It is possible to create an instance
of deprecator. Backwards compatibility has been preserved.
You can choose which instance of the deprecator will be used.
deprecate :method_name, deprecator: deprecator_instance
- You can use ActiveSupport::Deprecation in your gem.
+ You can use `ActiveSupport::Deprecation` in your gem.
require 'active_support/deprecation'
require 'active_support/core_ext/module/deprecation'
@@ -272,7 +317,7 @@
* `ERB::Util.html_escape` encodes single quote as `#39`. Decimal form has better support in old browsers. *Kalys Osmonov*
* `ActiveSupport::Callbacks`: deprecate monkey patch of object callbacks.
- Using the #filter method like this:
+ Using the `filter` method like this:
before_filter MyFilter.new
@@ -305,7 +350,7 @@
*Akira Matsuda*
-* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore
+* Replace deprecated `memcache-client` gem with `dalli` in `ActiveSupport::Cache::MemCacheStore`.
*Guillermo Iguaran*
@@ -319,14 +364,14 @@
*Erich Menge*
-* Add String#indent. *fxn & Ace Suares*
+* Add `String#indent`. *fxn & Ace Suares*
* Inflections can now be defined per locale. `singularize` and `pluralize`
accept locale as an extra argument.
*David Celis*
-* `Object#try` will now return nil instead of raise a NoMethodError if the
+* `Object#try` will now return `nil` instead of raise a `NoMethodError` if the
receiving object does not implement the method, but you can still get the
old behavior by using the new `Object#try!`.
@@ -354,62 +399,64 @@
*Francesco Rodriguez*
-* ActionView::Helpers::NumberHelper methods have been moved to ActiveSupport::NumberHelper and are now available via
- Numeric#to_s. Numeric#to_s now accepts the formatting options :phone, :currency, :percentage, :delimited,
- :rounded, :human, and :human_size. *Andrew Mutz*
+* `ActionView::Helpers::NumberHelper` methods have been moved to `ActiveSupport::NumberHelper` and are now available via
+ `Numeric#to_s`. `Numeric#to_s` now accepts the formatting options `:phone`, `:currency`, `:percentage`, `:delimited`,
+ `:rounded`, `:human`, and `:human_size`.
+
+ *Andrew Mutz*
* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_transform_keys!`. *Mark McSpadden*
-* Changed xml type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri*
+* Changed XML type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri*
* Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov*
* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White*
-* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a +Hash+ instance into strings *Lucas Húngaro*
+* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a `Hash` instance into strings. *Lucas Húngaro*
-* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a +Hash+ instance into symbols *Lucas Húngaro*
+* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a `Hash` instance into symbols. *Lucas Húngaro*
* `Object#try` can't call private methods. *Vasiliy Ermolovich*
* `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez*
-* `deep_dup` works more expectedly now and duplicates also values in +Hash+ instances and elements in +Array+ instances. *Alexey Gaziev*
+* `deep_dup` works more expectedly now and duplicates also values in `Hash` instances and elements in `Array` instances. *Alexey Gaziev*
-* Inflector no longer applies ice -> ouse to words like slice, police, ets *Wes Morgan*
+* Inflector no longer applies ice -> ouse to words like "slice", "police", etc. *Wes Morgan*
-* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations *twinturbo*
+* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations. *twinturbo*
-* Make Module#delegate stop using `send` - can no longer delegate to private methods. *dasch*
+* Make `Module#delegate` stop using `send` - can no longer delegate to private methods. *dasch*
-* AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev*
+* `ActiveSupport::Callbacks`: deprecate `:rescuable` option. *Bogdan Gusiev*
-* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea*
+* Adds `Integer#ordinal` to get the ordinal suffix string of an integer. *Tim Gildea*
-* AS::Callbacks: `:per_key` option is no longer supported
+* `ActiveSupport::Callbacks`: `:per_key` option is no longer supported. *Bogdan Gusiev*
-* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option.
+* `ActiveSupport::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. *Bogdan Gusiev*
-* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva*
+* Add `html_escape_once` to `ERB::Util`, and delegate the `escape_once` tag helper to it. *Carlos Antonio da Silva*
-* Deprecates the compatibility method Module#local_constant_names,
- use Module#local_constants instead (which returns symbols). *fxn*
+* Deprecates the compatibility method `Module#local_constant_names`,
+ use `Module#local_constants` instead (which returns symbols). *Xavier Noria*
-* Deletes the compatibility method Module#method_names,
- use Module#methods from now on (which returns symbols). *fxn*
+* Deletes the compatibility method `Module#method_names`,
+ use `Module#methods` from now on (which returns symbols). *Xavier Noria*
-* Deletes the compatibility method Module#instance_method_names,
- use Module#instance_methods from now on (which returns symbols). *fxn*
+* Deletes the compatibility method `Module#instance_method_names`,
+ use `Module#instance_methods` from now on (which returns symbols). *Xavier Noria*
-* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger
- from Ruby stdlib.
+* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger
+ from the Ruby standard library.
-* Unicode database updated to 6.1.0.
+ *Aaron Patterson*
-* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead
- of wrapping them in strings for safety.
+* Unicode database updated to 6.1.0. *Norman Clarke*
-* Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge*
+* Adds `encode_big_decimal_as_string` option to force JSON serialization of `BigDecimal` as numeric instead
+ of wrapping them in strings for safety.
* Optimize log subscribers to check log level before doing any processing. *Brian Durand*
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 1d3682eaf2..9f0864d9bb 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -59,7 +59,7 @@ class DateTime
options.fetch(:day, day),
options.fetch(:hour, hour),
options.fetch(:min, options[:hour] ? 0 : min),
- options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction),
options.fetch(:offset, offset),
options.fetch(:start, start)
)
@@ -124,6 +124,18 @@ class DateTime
end
alias :at_end_of_hour :end_of_hour
+ # Returns a new DateTime representing the start of the minute (hh:mm:00).
+ def beginning_of_minute
+ change(:sec => 0)
+ end
+ alias :at_beginning_of_minute :beginning_of_minute
+
+ # Returns a new DateTime representing the end of the minute (hh:mm:59).
+ def end_of_minute
+ change(:sec => 59)
+ end
+ alias :at_end_of_minute :end_of_minute
+
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0.
#
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 1f95f62229..a3ce7dbe3f 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -188,6 +188,21 @@ class Time
end
alias :at_end_of_hour :end_of_hour
+ # Returns a new Time representing the start of the minute (x:xx:00)
+ def beginning_of_minute
+ change(:sec => 0)
+ end
+ alias :at_beginning_of_minute :beginning_of_minute
+
+ # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9)
+ def end_of_minute
+ change(
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
+ end
+ alias :at_end_of_minute :end_of_minute
+
# Returns a Range representing the whole day of the current time.
def all_day
beginning_of_day..end_of_day
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 2191471daa..cc935e6cb9 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -580,7 +580,7 @@ module ActiveSupport
unit = case units
when Hash
- units[DECIMAL_UNITS[display_exponent]]
+ units[DECIMAL_UNITS[display_exponent]] || ''
when String, Symbol
I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
else
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 0e6d12a186..98c866ac43 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -317,6 +317,10 @@ module ActiveSupport
end
alias_method :tv_sec, :to_i
+ def to_r
+ utc.to_r
+ end
+
# Return an instance of Time in the system timezone.
def to_time
utc.to_time
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 24e62cc2b9..7be578599b 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -88,6 +88,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour
end
+ def test_beginning_of_minute
+ assert_equal DateTime.civil(2005,2,4,19,30,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_minute
+ end
+
+ def test_end_of_minute
+ assert_equal DateTime.civil(2005,2,4,19,30,59), DateTime.civil(2005,2,4,19,30,10).end_of_minute
+ end
+
def test_end_of_month
assert_equal DateTime.civil(2005,3,31,23,59,59), DateTime.civil(2005,3,20,10,10,10).end_of_month
assert_equal DateTime.civil(2005,2,28,23,59,59), DateTime.civil(2005,2,20,10,10,10).end_of_month
@@ -121,6 +129,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2005,2,22,16), DateTime.civil(2005,2,22,15,15,10).change(:hour => 16)
assert_equal DateTime.civil(2005,2,22,16,45), DateTime.civil(2005,2,22,15,15,10).change(:hour => 16, :min => 45)
assert_equal DateTime.civil(2005,2,22,15,45), DateTime.civil(2005,2,22,15,15,10).change(:min => 45)
+
+ # datetime with fractions of a second
+ assert_equal DateTime.civil(2005,2,1,15,15,10.7), DateTime.civil(2005,2,22,15,15,10.7).change(:day => 1)
end
def test_advance
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 43c92003dc..2864d7a57f 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -121,6 +121,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour
end
+ def test_beginning_of_minute
+ assert_equal Time.local(2005,2,4,19,30,0), Time.local(2005,2,4,19,30,10).beginning_of_minute
+ end
+
def test_end_of_day
assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day
with_env_tz 'US/Eastern' do
@@ -137,6 +141,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour
end
+ def test_end_of_minute
+ assert_equal Time.local(2005,2,4,19,30,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_minute
+ end
+
def test_last_year
assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year
end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index c2b3676aac..0f5699fd63 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -326,6 +326,17 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal 946684800, twz.to_i
end
+ def test_to_r
+ result = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']).to_r
+ assert_equal Rational(946684800, 1), result
+ assert_kind_of Rational, result
+ end
+
+ def test_time_at
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii'])
+ assert_equal time, Time.at(time)
+ end
+
def test_to_time
with_env_tz 'US/Eastern' do
assert_equal Time, @twz.to_time.class
@@ -539,6 +550,20 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect
end
+ def test_beginning_of_minute
+ utc = Time.utc(2000, 1, 1, 0, 30, 10)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:10 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect
+ end
+
+ def test_end_of_minute
+ utc = Time.utc(2000, 1, 1, 0, 30, 10)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:10 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:30:59 EST -05:00", twz.end_of_minute.inspect
+ end
+
def test_since
assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 5f54587f93..1fadef3637 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -301,6 +301,13 @@ module ActiveSupport
end
end
+ def test_number_to_human_with_custom_units_that_are_missing_the_needed_key
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '123', number_helper.number_to_human(123, units: { thousand: 'k'})
+ assert_equal '123', number_helper.number_to_human(123, units: {})
+ end
+ end
+
def test_number_to_human_with_custom_format
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
assert_equal '123 times Thousand', number_helper.number_to_human(123456, :format => "%n times %u")
diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb
index ed2667ac58..d169e9452c 100644
--- a/guides/code/getting_started/config/environments/development.rb
+++ b/guides/code/getting_started/config/environments/development.rb
@@ -22,10 +22,6 @@ Blog::Application.configure do
# Only use best-standards-support built into browsers.
config.action_dispatch.best_standards_support = :builtin
- # Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL).
- config.active_record.auto_explain_threshold_in_seconds = 0.5
-
# Raise an error on page load if there are pending migrations
config.active_record.migration_error = :page_load
diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb
index 58dab2a319..368a735122 100644
--- a/guides/code/getting_started/config/environments/production.rb
+++ b/guides/code/getting_started/config/environments/production.rb
@@ -72,10 +72,6 @@ Blog::Application.configure do
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
- # Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL).
- # config.active_record.auto_explain_threshold_in_seconds = 0.5
-
# Disable automatic flushing of the log to improve performance.
# config.autoflush_log = false
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index 9c157ec0b3..463da488f2 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -200,6 +200,8 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ
* Remove IdentityMap.
+* Remove automatic execution of EXPLAIN queries. The option `active_record.auto_explain_threshold_in_seconds` is no longer used and should be removed.
+
* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class.
* Added `create_join_table` migration helper to create HABTM join tables.
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 516457bcd3..bb42fab101 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -342,19 +342,17 @@ By using the `after_commit` callback we can account for this case.
```ruby
class PictureFile < ActiveRecord::Base
- attr_accessor :delete_file
+ after_commit :delete_picture_file_from_disk, :on => [:destroy]
- after_destroy do |picture_file|
- picture_file.delete_file = picture_file.filepath
- end
-
- after_commit do |picture_file|
- if picture_file.delete_file && File.exist?(picture_file.delete_file)
- File.delete(picture_file.delete_file)
- picture_file.delete_file = nil
+ def delete_picture_file_from_disk
+ if File.exist?(filepath)
+ File.delete(filepath)
end
end
end
```
+NOTE: the `:on` option specifies when a callback will be fired. If you
+don't supply the `:on` option the callback will fire for every action.
+
The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback.
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 62d6294ae5..bc3b1669d2 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1609,45 +1609,6 @@ EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
under MySQL.
-### Automatic EXPLAIN
-
-Active Record is able to run EXPLAIN automatically on slow queries and log its
-output. This feature is controlled by the configuration parameter
-
-```ruby
-config.active_record.auto_explain_threshold_in_seconds
-```
-
-If set to a number, any query exceeding those many seconds will have its EXPLAIN
-automatically triggered and logged. In the case of relations, the threshold is
-compared to the total time needed to fetch records. So, a relation is seen as a
-unit of work, no matter whether the implementation of eager loading involves
-several queries under the hood.
-
-A threshold of `nil` disables automatic EXPLAINs.
-
-The default threshold in development mode is 0.5 seconds, and `nil` in test and
-production modes.
-
-INFO. Automatic EXPLAIN gets disabled if Active Record has no logger, regardless
-of the value of the threshold.
-
-#### Disabling Automatic EXPLAIN
-
-Automatic EXPLAIN can be selectively silenced with `ActiveRecord::Base.silence_auto_explain`:
-
-```ruby
-ActiveRecord::Base.silence_auto_explain do
- # no automatic EXPLAIN is triggered here
-end
-```
-
-That may be useful for queries you know are slow but fine, like a heavyweight
-report of an admin interface.
-
-As its name suggests, `silence_auto_explain` only silences automatic EXPLAINs.
-Explicit calls to `ActiveRecord::Relation#explain` run.
-
### Interpreting EXPLAIN
Interpretation of the output of EXPLAIN is beyond the scope of this guide. The
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index be46e15078..dbbeec7126 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -268,8 +268,6 @@ config.middleware.delete "Rack::MethodOverride"
* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default.
-* `config.active_record.auto_explain_threshold_in_seconds` configures the threshold for automatic EXPLAINs (`nil` disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
-
* +config.active_record.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+.
The MySQL adapter adds one additional configuration option:
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index c4fbae8925..d738d847e9 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -344,6 +344,16 @@ create_join_table :products, :categories, column_options: {null: true}
will create the `product_id` and `category_id` with the `:null` option as
`true`.
+`create_join_table` also accepts a block, which you can use to add indices
+(which are not created by default) or additional columns:
+
+```ruby
+create_join_table :products, :categories do |t|
+ t.index :products
+ t.index :categories
+end
+```
+
### Changing Tables
A close cousin of `create_table` is `change_table`, used for changing existing
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 18baed55b5..915a008a92 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -45,6 +45,12 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep
* Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`.
+* Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) to a smoothly upgrade path.
+
+### Active Resource
+
+Rails 4.0 extracted Active Resource to its our gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile.
+
### Active Model
* Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. Now when confirmation validations fail the error will be attached to `:#{attribute}_confirmation` instead of `attribute`.
diff --git a/rails.gemspec b/rails.gemspec
index 34957c9455..770847e87e 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -27,5 +27,5 @@ Gem::Specification.new do |s|
s.add_dependency 'railties', version
s.add_dependency 'bundler', '>= 1.2.2', '< 2.0'
- s.add_dependency 'sprockets-rails', '~> 2.0.0.rc1'
+ s.add_dependency 'sprockets-rails', '~> 2.0.0.rc3'
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index e2f2148911..fc362837d6 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,4 +1,16 @@
## Rails 4.0.0 (unreleased) ##
+
+* Improve `rake stats` for JavaScript and CoffeeScript: ignore block comments
+ and calculates number of functions.
+
+ *Hendy Tanata*
+
+* Ability to use a custom builder by passing `--builder` (or `-b`) has been removed. Consider
+ using application template instead. See this guide for more detail:
+ http://guides.rubyonrails.org/rails_application_templates.html
+
+ *Prem Sichanugrist*
+
* fix rake db:* tasks to work with DATABASE_URL and without config/database.yml
*Terence Lee*
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb
index 8937e10db3..44f4d3dabc 100644
--- a/railties/lib/rails/app_rails_loader.rb
+++ b/railties/lib/rails/app_rails_loader.rb
@@ -3,12 +3,16 @@ require 'pathname'
module Rails
module AppRailsLoader
RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
- EXECUTABLE = 'bin/rails'
+ EXECUTABLES = ['bin/rails', 'script/rails']
def self.exec_app_rails
- cwd = Dir.pwd
- return unless in_rails_application_or_engine? || in_rails_application_or_engine_subdirectory?
- exec RUBY, EXECUTABLE, *ARGV if in_rails_application_or_engine?
+ cwd = Dir.pwd
+
+ exe = find_executable
+ exe ||= find_executable_in_parent_path
+ return unless exe
+
+ exec RUBY, exe, *ARGV if find_executable
Dir.chdir("..") do
# Recurse in a chdir block: if the search fails we want to be sure
# the application is generated in the original working directory.
@@ -18,12 +22,16 @@ module Rails
# could not chdir, no problem just return
end
- def self.in_rails_application_or_engine?
- File.exists?(EXECUTABLE) && File.read(EXECUTABLE) =~ /(APP|ENGINE)_PATH/
+ def self.find_executable
+ EXECUTABLES.find do |exe|
+ File.exists?(exe) && File.read(exe) =~ /(APP|ENGINE)_PATH/
+ end
end
- def self.in_rails_application_or_engine_subdirectory?(path = Pathname.new(Dir.pwd))
- File.exists?(File.join(path, EXECUTABLE)) || !path.root? && in_rails_application_or_engine_subdirectory?(path.parent)
+ def self.find_executable_in_parent_path(path = Pathname.new(Dir.pwd))
+ EXECUTABLES.find do |exe|
+ File.exists?(File.join(path, exe)) || !path.root? && find_executable_in_parent_path(path.parent)
+ end
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 1b88b834c7..17763b39c5 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -97,9 +97,9 @@ module Rails
self
end
- # Loads and returns the contents of the #database_configuration_file. The
- # contents of the file are processed via ERB before being sent through
- # YAML::load.
+ # Loads and returns the configuration of the database.
+ # First, looks at If ENV['DATABASE_URL'] if it's not present it uses the #paths["config/database"]
+ # The contents of the file are processed via ERB before being sent through YAML::load.
def database_configuration
if ENV['DATABASE_URL']
{Rails.env => ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.connection_url_to_hash(ENV['DATABASE_URL']).stringify_keys}
diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb
index b717b026de..e5341ac436 100644
--- a/railties/lib/rails/cli.rb
+++ b/railties/lib/rails/cli.rb
@@ -3,9 +3,6 @@ require 'rails/app_rails_loader'
# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
-#
-# TODO: when we hit this, advise adding ./bin to $PATH instead. Then the
-# app's `rails` executable is run immediately.
Rails::AppRailsLoader.exec_app_rails
require 'rails/ruby_version_check'
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 039360fcf6..0ae6d2a455 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -1,3 +1,5 @@
+require 'rails/code_statistics_calculator'
+
class CodeStatistics #:nodoc:
TEST_TYPES = ['Controller tests',
@@ -33,64 +35,38 @@ class CodeStatistics #:nodoc:
end
def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee)$/)
- stats = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
+ stats = CodeStatisticsCalculator.new
Dir.foreach(directory) do |file_name|
- if File.directory?(directory + "/" + file_name) and (/^\./ !~ file_name)
- newstats = calculate_directory_statistics(directory + "/" + file_name, pattern)
- stats.each { |k, v| stats[k] += newstats[k] }
+ path = "#{directory}/#{file_name}"
+
+ if File.directory?(path) && (/^\./ !~ file_name)
+ stats.add(calculate_directory_statistics(path, pattern))
end
next unless file_name =~ pattern
- comment_started = false
-
- case file_name
- when /.*\.js$/
- comment_pattern = /^\s*\/\//
- else
- comment_pattern = /^\s*#/
- end
-
- File.open(directory + "/" + file_name) do |f|
- while line = f.gets
- stats["lines"] += 1
- if(comment_started)
- if line =~ /^=end/
- comment_started = false
- end
- next
- else
- if line =~ /^=begin/
- comment_started = true
- next
- end
- end
- stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/
- stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/
- stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ comment_pattern
- end
- end
+ stats.add_by_file_path(path)
end
stats
end
def calculate_total
- total = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
- @statistics.each_value { |pair| pair.each { |k, v| total[k] += v } }
- total
+ @statistics.each_with_object(CodeStatisticsCalculator.new) do |pair, total|
+ total.add(pair.last)
+ end
end
def calculate_code
code_loc = 0
- @statistics.each { |k, v| code_loc += v['codelines'] unless TEST_TYPES.include? k }
+ @statistics.each { |k, v| code_loc += v.code_lines unless TEST_TYPES.include? k }
code_loc
end
def calculate_tests
test_loc = 0
- @statistics.each { |k, v| test_loc += v['codelines'] if TEST_TYPES.include? k }
+ @statistics.each { |k, v| test_loc += v.code_lines if TEST_TYPES.include? k }
test_loc
end
@@ -105,15 +81,15 @@ class CodeStatistics #:nodoc:
end
def print_line(name, statistics)
- m_over_c = (statistics["methods"] / statistics["classes"]) rescue m_over_c = 0
- loc_over_m = (statistics["codelines"] / statistics["methods"]) - 2 rescue loc_over_m = 0
-
- puts "| #{name.ljust(20)} " +
- "| #{statistics["lines"].to_s.rjust(5)} " +
- "| #{statistics["codelines"].to_s.rjust(5)} " +
- "| #{statistics["classes"].to_s.rjust(7)} " +
- "| #{statistics["methods"].to_s.rjust(7)} " +
- "| #{m_over_c.to_s.rjust(3)} " +
+ m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0
+ loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0
+
+ puts "| #{name.ljust(20)} " \
+ "| #{statistics.lines.to_s.rjust(5)} " \
+ "| #{statistics.code_lines.to_s.rjust(5)} " \
+ "| #{statistics.classes.to_s.rjust(7)} " \
+ "| #{statistics.methods.to_s.rjust(7)} " \
+ "| #{m_over_c.to_s.rjust(3)} " \
"| #{loc_over_m.to_s.rjust(5)} |"
end
diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb
new file mode 100644
index 0000000000..60e4aef9b7
--- /dev/null
+++ b/railties/lib/rails/code_statistics_calculator.rb
@@ -0,0 +1,79 @@
+class CodeStatisticsCalculator #:nodoc:
+ attr_reader :lines, :code_lines, :classes, :methods
+
+ PATTERNS = {
+ rb: {
+ line_comment: /^\s*#/,
+ begin_block_comment: /^=begin/,
+ end_block_comment: /^=end/,
+ class: /^\s*class\s+[_A-Z]/,
+ method: /^\s*def\s+[_a-z]/,
+ },
+ js: {
+ line_comment: %r{^\s*//},
+ begin_block_comment: %r{^\s*/\*},
+ end_block_comment: %r{\*/},
+ method: /function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/,
+ },
+ coffee: {
+ line_comment: /^\s*#/,
+ begin_block_comment: /^\s*###/,
+ end_block_comment: /^\s*###/,
+ class: /^\s*class\s+[_A-Z]/,
+ method: /[-=]>/,
+ }
+ }
+
+ def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0)
+ @lines = lines
+ @code_lines = code_lines
+ @classes = classes
+ @methods = methods
+ end
+
+ def add(code_statistics_calculator)
+ @lines += code_statistics_calculator.lines
+ @code_lines += code_statistics_calculator.code_lines
+ @classes += code_statistics_calculator.classes
+ @methods += code_statistics_calculator.methods
+ end
+
+ def add_by_file_path(file_path)
+ File.open(file_path) do |f|
+ self.add_by_io(f, file_type(file_path))
+ end
+ end
+
+ def add_by_io(io, file_type)
+ patterns = PATTERNS[file_type] || {}
+
+ comment_started = false
+
+ while line = io.gets
+ @lines += 1
+
+ if comment_started
+ if patterns[:end_block_comment] && line =~ patterns[:end_block_comment]
+ comment_started = false
+ end
+ next
+ else
+ if patterns[:begin_block_comment] && line =~ patterns[:begin_block_comment]
+ comment_started = true
+ next
+ end
+ end
+
+ @classes += 1 if patterns[:class] && line =~ patterns[:class]
+ @methods += 1 if patterns[:method] && line =~ patterns[:method]
+ if line !~ /^\s*$/ && (patterns[:line_comment].nil? || line !~ patterns[:line_comment])
+ @code_lines += 1
+ end
+ end
+ end
+
+ private
+ def file_type(file_path)
+ File.extname(file_path).sub(/\A\./, '').downcase.to_sym
+ end
+end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 99d80b3245..3080b055a0 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -19,9 +19,6 @@ module Rails
argument :app_path, type: :string
def self.add_shared_options_for(name)
- class_option :builder, type: :string, aliases: '-b',
- desc: "Path to some #{name} builder (can be a filesystem path or URL)"
-
class_option :template, type: :string, aliases: '-m',
desc: "Path to some #{name} template (can be a filesystem path or URL)"
@@ -81,17 +78,6 @@ module Rails
def builder
@builder ||= begin
- if path = options[:builder]
- if URI(path).is_a?(URI::HTTP)
- contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read }
- else
- contents = open(File.expand_path(path, @original_wd)) {|io| io.read }
- end
-
- prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1)
- instance_eval(&prok)
- end
-
builder_class = get_builder_class
builder_class.send(:include, ActionMethods)
builder_class.new(self)
@@ -210,7 +196,7 @@ module Rails
# Gems used only for assets and not required
# in production environments by default.
group :assets do
- gem 'sprockets-rails', '~> 2.0.0.rc1'
+ gem 'sprockets-rails', '~> 2.0.0.rc3'
gem 'sass-rails', '~> 4.0.0.beta'
gem 'coffee-rails', '~> 4.0.0.beta'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index c4cc1162a4..d0e62d09cc 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -20,10 +20,6 @@
config.active_support.deprecation = :log
<%- unless options.skip_active_record? -%>
- # Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL).
- config.active_record.auto_explain_threshold_in_seconds = 0.5
-
# Raise an error on page load if there are pending migrations
config.active_record.migration_error = :page_load
<%- end -%>
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 0ab91d9864..5669fe6d64 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
@@ -76,12 +76,6 @@
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
- <%- unless options.skip_active_record? -%>
- # Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL).
- # config.active_record.auto_explain_threshold_in_seconds = 0.5
- <%- end -%>
-
# Disable automatic flushing of the log to improve performance.
# config.autoflush_log = false
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index ea6c074bdc..0057b0f887 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -102,7 +102,7 @@ namespace :doc do
# desc "Generate Rails Guides"
task :guides do
# FIXME: Reaching outside lib directory is a bad idea
- require File.expand_path('../../../../guides/rails_guides', __FILE__)
+ require File.expand_path('../../../../../guides/rails_guides', __FILE__)
RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate
end
end
diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_rails_loader_test.rb
index 87e0ad7bd7..63ed9eaef0 100644
--- a/railties/test/app_rails_loader_test.rb
+++ b/railties/test/app_rails_loader_test.rb
@@ -2,40 +2,47 @@ require 'abstract_unit'
require 'rails/app_rails_loader'
class AppRailsLoaderTest < ActiveSupport::TestCase
- test "is in a rails application if bin/rails exists and contains APP_PATH" do
- File.stubs(:exists?).returns(true)
- File.stubs(:read).with('bin/rails').returns('APP_PATH')
- assert Rails::AppRailsLoader.in_rails_application_or_engine?
- end
- test "is not in a rails application if bin/rails exists but doesn't contain APP_PATH" do
- File.stubs(:exists?).returns(true)
- File.stubs(:read).with('bin/rails').returns('railties bin/rails')
- assert !Rails::AppRailsLoader.in_rails_application_or_engine?
+ setup do
+ File.stubs(:exists?).returns(false)
end
- test "is in a rails application if parent directory has bin/rails containing APP_PATH" do
- File.stubs(:exists?).with("/foo/bar/bin/rails").returns(false)
- File.stubs(:exists?).with("/foo/bin/rails").returns(true)
- File.stubs(:read).with('/foo/bin/rails').returns('APP_PATH')
- assert Rails::AppRailsLoader.in_rails_application_or_engine_subdirectory?(Pathname.new("/foo/bar"))
- end
+ ['bin/rails', 'script/rails'].each do |exe|
+ test "is in a rails application if #{exe} exists and contains APP_PATH" do
+ File.stubs(:exists?).with(exe).returns(true)
+ File.stubs(:read).with(exe).returns('APP_PATH')
+ assert Rails::AppRailsLoader.find_executable
+ end
- test "is not in a rails application if at the root directory and doesn't have bin/rails" do
- Pathname.any_instance.stubs(:root?).returns true
- assert !Rails::AppRailsLoader.in_rails_application_or_engine?
- end
+ test "is not in a rails application if #{exe} exists but doesn't contain APP_PATH" do
+ File.stubs(:exists?).with(exe).returns(true)
+ File.stubs(:read).with(exe).returns("railties #{exe}")
+ assert !Rails::AppRailsLoader.find_executable
+ end
- test "is in a rails engine if parent directory has bin/rails containing ENGINE_PATH" do
- File.stubs(:exists?).with("/foo/bar/bin/rails").returns(false)
- File.stubs(:exists?).with("/foo/bin/rails").returns(true)
- File.stubs(:read).with('/foo/bin/rails').returns('ENGINE_PATH')
- assert Rails::AppRailsLoader.in_rails_application_or_engine_subdirectory?(Pathname.new("/foo/bar"))
- end
+ test "is in a rails application if parent directory has #{exe} containing APP_PATH" do
+ File.stubs(:exists?).with("/foo/bar/#{exe}").returns(false)
+ File.stubs(:exists?).with("/foo/#{exe}").returns(true)
+ File.stubs(:read).with("/foo/#{exe}").returns('APP_PATH')
+ assert Rails::AppRailsLoader.find_executable_in_parent_path(Pathname.new("/foo/bar"))
+ end
+
+ test "is not in a rails application if at the root directory and doesn't have #{exe}" do
+ Pathname.any_instance.stubs(:root?).returns true
+ assert !Rails::AppRailsLoader.find_executable
+ end
+
+ test "is in a rails engine if parent directory has #{exe} containing ENGINE_PATH" do
+ File.stubs(:exists?).with("/foo/bar/#{exe}").returns(false)
+ File.stubs(:exists?).with("/foo/#{exe}").returns(true)
+ File.stubs(:read).with("/foo/#{exe}").returns('ENGINE_PATH')
+ assert Rails::AppRailsLoader.find_executable_in_parent_path(Pathname.new("/foo/bar"))
+ end
- test "is in a rails engine if bin/rails exists containing ENGINE_PATH" do
- File.stubs(:exists?).returns(true)
- File.stubs(:read).with('bin/rails').returns('ENGINE_PATH')
- assert Rails::AppRailsLoader.in_rails_application_or_engine?
+ test "is in a rails engine if #{exe} exists containing ENGINE_PATH" do
+ File.stubs(:exists?).with(exe).returns(true)
+ File.stubs(:read).with(exe).returns('ENGINE_PATH')
+ assert Rails::AppRailsLoader.find_executable
+ end
end
end
diff --git a/railties/test/application/initializers/boot_test.rb b/railties/test/application/initializers/boot_test.rb
deleted file mode 100644
index 7eda15894e..0000000000
--- a/railties/test/application/initializers/boot_test.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require "isolation/abstract_unit"
-
-module ApplicationTests
- class BootTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- # build_app
- # boot_rails
- end
-
- def teardown
- # teardown_app
- end
-
- test "booting rails sets the load paths correctly" do
- # This test is pending reworking the boot process
- end
- end
-end
diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb
new file mode 100644
index 0000000000..b3eabf5024
--- /dev/null
+++ b/railties/test/code_statistics_calculator_test.rb
@@ -0,0 +1,288 @@
+require 'abstract_unit'
+require 'rails/code_statistics_calculator'
+
+class CodeStatisticsCalculatorTest < ActiveSupport::TestCase
+ def setup
+ @code_statistics_calculator = CodeStatisticsCalculator.new
+ end
+
+ test 'add statistics to another using #add' do
+ code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4)
+ @code_statistics_calculator.add(code_statistics_calculator_1)
+
+ assert_equal 1, @code_statistics_calculator.lines
+ assert_equal 2, @code_statistics_calculator.code_lines
+ assert_equal 3, @code_statistics_calculator.classes
+ assert_equal 4, @code_statistics_calculator.methods
+
+ code_statistics_calculator_2 = CodeStatisticsCalculator.new(2, 3, 4, 5)
+ @code_statistics_calculator.add(code_statistics_calculator_2)
+
+ assert_equal 3, @code_statistics_calculator.lines
+ assert_equal 5, @code_statistics_calculator.code_lines
+ assert_equal 7, @code_statistics_calculator.classes
+ assert_equal 9, @code_statistics_calculator.methods
+ end
+
+ test 'accumulate statistics using #add_by_io' do
+ code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4)
+ @code_statistics_calculator.add(code_statistics_calculator_1)
+
+ code = <<-'CODE'
+ def foo
+ puts 'foo'
+ end
+
+ def bar; end
+ class A; end
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
+
+ assert_equal 7, @code_statistics_calculator.lines
+ assert_equal 7, @code_statistics_calculator.code_lines
+ assert_equal 4, @code_statistics_calculator.classes
+ assert_equal 6, @code_statistics_calculator.methods
+ end
+
+ test 'calculate statistics using #add_by_file_path' do
+ tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp'))
+ FileUtils.mkdir_p(tmp_path)
+
+ code = <<-'CODE'
+ def foo
+ puts 'foo'
+ # bar
+ end
+ CODE
+
+ file_path = "#{tmp_path}/stats.rb"
+ File.open(file_path, 'w') { |f| f.write(code) }
+
+ @code_statistics_calculator.add_by_file_path(file_path)
+
+ assert_equal 4, @code_statistics_calculator.lines
+ assert_equal 3, @code_statistics_calculator.code_lines
+ assert_equal 0, @code_statistics_calculator.classes
+ assert_equal 1, @code_statistics_calculator.methods
+
+ FileUtils.rm_rf(tmp_path)
+ end
+
+ test 'calculate number of Ruby methods' do
+ code = <<-'CODE'
+ def foo
+ puts 'foo'
+ end
+
+ def bar; end
+
+ class Foo
+ def bar(abc)
+ end
+ end
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
+
+ assert_equal 3, @code_statistics_calculator.methods
+ end
+
+ test 'calculate Ruby LOCs' do
+ code = <<-'CODE'
+ def foo
+ puts 'foo'
+ end
+
+ # def bar; end
+
+ class A < B
+ end
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
+
+ assert_equal 8, @code_statistics_calculator.lines
+ assert_equal 5, @code_statistics_calculator.code_lines
+ end
+
+ test 'calculate number of Ruby classes' do
+ code = <<-'CODE'
+ class Foo < Bar
+ def foo
+ puts 'foo'
+ end
+ end
+
+ class Z; end
+
+ # class A
+ # end
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
+
+ assert_equal 2, @code_statistics_calculator.classes
+ end
+
+ test 'skip Ruby comments' do
+ code = <<-'CODE'
+=begin
+ class Foo
+ def foo
+ puts 'foo'
+ end
+ end
+=end
+
+ # class A
+ # end
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
+
+ assert_equal 10, @code_statistics_calculator.lines
+ assert_equal 0, @code_statistics_calculator.code_lines
+ assert_equal 0, @code_statistics_calculator.classes
+ assert_equal 0, @code_statistics_calculator.methods
+ end
+
+ test 'calculate number of JS methods' do
+ code = <<-'CODE'
+ function foo(x, y, z) {
+ doX();
+ }
+
+ $(function () {
+ bar();
+ })
+
+ var baz = function ( x ) {
+ }
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :js)
+
+ assert_equal 3, @code_statistics_calculator.methods
+ end
+
+ test 'calculate JS LOCs' do
+ code = <<-'CODE'
+ function foo()
+ alert('foo');
+ end
+
+ // var b = 2;
+
+ var a = 1;
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :js)
+
+ assert_equal 7, @code_statistics_calculator.lines
+ assert_equal 4, @code_statistics_calculator.code_lines
+ end
+
+ test 'skip JS comments' do
+ code = <<-'CODE'
+ /*
+ * var f = function () {
+ 1 / 2;
+ }
+ */
+
+ // call();
+ //
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :js)
+
+ assert_equal 8, @code_statistics_calculator.lines
+ assert_equal 0, @code_statistics_calculator.code_lines
+ assert_equal 0, @code_statistics_calculator.classes
+ assert_equal 0, @code_statistics_calculator.methods
+ end
+
+ test 'calculate number of CoffeeScript methods' do
+ code = <<-'CODE'
+ square = (x) -> x * x
+
+ math =
+ cube: (x) -> x * square x
+
+ fill = (container, liquid = "coffee") ->
+ "Filling the #{container} with #{liquid}..."
+
+ $('.shopping_cart').bind 'click', (event) =>
+ @customer.purchase @cart
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee)
+
+ assert_equal 4, @code_statistics_calculator.methods
+ end
+
+ test 'calculate CoffeeScript LOCs' do
+ code = <<-'CODE'
+ # Assignment:
+ number = 42
+ opposite = true
+
+ ###
+ CoffeeScript Compiler v1.4.0
+ Released under the MIT License
+ ###
+
+ # Conditions:
+ number = -42 if opposite
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee)
+
+ assert_equal 11, @code_statistics_calculator.lines
+ assert_equal 3, @code_statistics_calculator.code_lines
+ end
+
+ test 'calculate number of CoffeeScript classes' do
+ code = <<-'CODE'
+ class Animal
+ constructor: (@name) ->
+
+ move: (meters) ->
+ alert @name + " moved #{meters}m."
+
+ class Snake extends Animal
+ move: ->
+ alert "Slithering..."
+ super 5
+
+ # class Horse
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee)
+
+ assert_equal 2, @code_statistics_calculator.classes
+ end
+
+ test 'skip CoffeeScript comments' do
+ code = <<-'CODE'
+###
+class Animal
+ constructor: (@name) ->
+
+ move: (meters) ->
+ alert @name + " moved #{meters}m."
+ ###
+
+ # class Horse
+ alert 'hello'
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee)
+
+ assert_equal 10, @code_statistics_calculator.lines
+ assert_equal 1, @code_statistics_calculator.code_lines
+ assert_equal 0, @code_statistics_calculator.classes
+ assert_equal 0, @code_statistics_calculator.methods
+ end
+end
diff --git a/railties/test/fixtures/lib/app_builders/empty_builder.rb b/railties/test/fixtures/lib/app_builders/empty_builder.rb
deleted file mode 100644
index babd9c2461..0000000000
--- a/railties/test/fixtures/lib/app_builders/empty_builder.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class AppBuilder
-end \ No newline at end of file
diff --git a/railties/test/fixtures/lib/app_builders/simple_builder.rb b/railties/test/fixtures/lib/app_builders/simple_builder.rb
deleted file mode 100644
index 993d3a2aa2..0000000000
--- a/railties/test/fixtures/lib/app_builders/simple_builder.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class AppBuilder
- def gitignore
- create_file ".gitignore", <<-R.strip
-foobar
- R
- end
-end
diff --git a/railties/test/fixtures/lib/app_builders/tweak_builder.rb b/railties/test/fixtures/lib/app_builders/tweak_builder.rb
deleted file mode 100644
index cb50be01cb..0000000000
--- a/railties/test/fixtures/lib/app_builders/tweak_builder.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class AppBuilder < Rails::AppBuilder
- def gitignore
- create_file ".gitignore", <<-R.strip
-foobar
- R
- end
-end
diff --git a/railties/test/fixtures/lib/plugin_builders/empty_builder.rb b/railties/test/fixtures/lib/plugin_builders/empty_builder.rb
deleted file mode 100644
index 5c5607621c..0000000000
--- a/railties/test/fixtures/lib/plugin_builders/empty_builder.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class PluginBuilder
-end
diff --git a/railties/test/fixtures/lib/plugin_builders/simple_builder.rb b/railties/test/fixtures/lib/plugin_builders/simple_builder.rb
deleted file mode 100644
index 08f6c5535d..0000000000
--- a/railties/test/fixtures/lib/plugin_builders/simple_builder.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class PluginBuilder
- def gitignore
- create_file ".gitignore", <<-R.strip
-foobar
- R
- end
-end
diff --git a/railties/test/fixtures/lib/plugin_builders/spec_builder.rb b/railties/test/fixtures/lib/plugin_builders/spec_builder.rb
deleted file mode 100644
index 2721429599..0000000000
--- a/railties/test/fixtures/lib/plugin_builders/spec_builder.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class PluginBuilder < Rails::PluginBuilder
- def test
- create_file "spec/spec_helper.rb"
- append_file "Rakefile", <<-EOF
-# spec tasks in rakefile
-
-task default: :spec
- EOF
- end
-
- def generate_test_dummy
- dummy_path("spec/dummy")
- super
- end
-
- def skip_test_unit?
- true
- end
-end
diff --git a/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb b/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb
deleted file mode 100644
index 1e801409a4..0000000000
--- a/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class PluginBuilder < Rails::PluginBuilder
- def gitignore
- create_file ".gitignore", <<-R.strip
-foobar
- R
- end
-end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 83f43d1025..0697035871 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -342,15 +342,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_generated_environments_file_for_auto_explain
- run_generator [destination_root, "--skip-active-record"]
- %w(development production).each do |env|
- assert_file "config/environments/#{env}.rb" do |file|
- assert_no_match %r(auto_explain_threshold_in_seconds), file
- end
- end
- end
-
def test_pretend_option
output = run_generator [File.join(destination_root, "myapp"), "--pretend"]
assert_no_match(/run bundle install/, output)
@@ -366,28 +357,3 @@ protected
assert_file "Gemfile", /^gem\s+["']#{gem}["']$/
end
end
-
-class CustomAppGeneratorTest < Rails::Generators::TestCase
- include GeneratorsTestHelper
- tests Rails::Generators::AppGenerator
-
- arguments [destination_root]
- include SharedCustomGeneratorTests
-
-protected
- def default_files
- ::DEFAULT_APP_FILES
- end
-
- def builders_dir
- "app_builders"
- end
-
- def builder_class
- :AppBuilder
- end
-
- def action(*args, &block)
- silence(:stdout) { generator.send(*args, &block) }
- end
-end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 4bf5a2f08b..904c1db57e 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -371,38 +371,3 @@ protected
::DEFAULT_PLUGIN_FILES
end
end
-
-class CustomPluginGeneratorTest < Rails::Generators::TestCase
- include GeneratorsTestHelper
- tests Rails::Generators::PluginNewGenerator
-
- destination File.join(Rails.root, "tmp/bukkits")
- arguments [destination_root]
- include SharedCustomGeneratorTests
-
- def test_overriding_test_framework
- FileUtils.cd(destination_root)
- run_generator([destination_root, "-b", "#{Rails.root}/lib/plugin_builders/spec_builder.rb"])
- assert_file 'spec/spec_helper.rb'
- assert_file 'spec/dummy'
- assert_file 'Rakefile', /task default: :spec/
- assert_file 'Rakefile', /# spec tasks in rakefile/
- end
-
-protected
- def default_files
- ::DEFAULT_PLUGIN_FILES
- end
-
- def builder_class
- :PluginBuilder
- end
-
- def builders_dir
- "plugin_builders"
- end
-
- def action(*args, &block)
- silence(:stdout){ generator.send(*args, &block) }
- end
-end
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 2081f4aba8..2724882a23 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -140,61 +140,3 @@ module SharedGeneratorTests
assert_no_file('app/mailers/.keep')
end
end
-
-module SharedCustomGeneratorTests
- def setup
- Rails.application = TestApp::Application
- super
- Rails::Generators::AppGenerator.instance_variable_set('@desc', nil)
- end
-
- def teardown
- super
- Rails::Generators::AppGenerator.instance_variable_set('@desc', nil)
- Object.class_eval do
- remove_const :AppBuilder if const_defined?(:AppBuilder)
- remove_const :PluginBuilder if const_defined?(:PluginBuilder)
- end
- Rails.application = TestApp::Application.instance
- end
-
- def test_builder_option_with_empty_app_builder
- FileUtils.cd(destination_root)
- run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/empty_builder.rb"])
- default_files.each{ |path| assert_no_file path }
- end
-
- def test_builder_option_with_simple_plugin_builder
- FileUtils.cd(destination_root)
- run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/simple_builder.rb"])
- (default_files - ['.gitignore']).each{ |path| assert_no_file path }
- assert_file ".gitignore", "foobar"
- end
-
- def test_builder_option_with_relative_path
- here = File.expand_path(File.dirname(__FILE__))
- FileUtils.cd(here)
- run_generator([destination_root, "-b", "../fixtures/lib/#{builders_dir}/simple_builder.rb"])
- FileUtils.cd(destination_root)
- (default_files - ['.gitignore']).each{ |path| assert_no_file path }
- assert_file ".gitignore", "foobar"
- end
-
- def test_builder_option_with_tweak_plugin_builder
- FileUtils.cd(destination_root)
- run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/tweak_builder.rb"])
- default_files.each{ |path| assert_file path }
- assert_file ".gitignore", "foobar"
- end
-
- def test_builder_option_with_http
- url = "https://gist.github.com/josevalim/103208/raw/"
- template = "class #{builder_class}; end"
- template.instance_eval "def read; self; end" # Make the string respond to read
-
- generator([destination_root], builder: url).expects(:open).with(url, 'Accept' => 'application/x-thor-template').returns(template)
- quietly { generator.invoke_all }
-
- default_files.each{ |path| assert_no_file(path) }
- end
-end