aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--CONTRIBUTING.md8
-rw-r--r--Gemfile3
-rw-r--r--RELEASING_RAILS.rdoc2
-rw-r--r--Rakefile2
-rw-r--r--actionmailer/lib/action_mailer/version.rb13
-rw-r--r--actionmailer/test/mailers/base_mailer.rb2
-rw-r--r--actionpack/CHANGELOG.md46
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb14
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb1
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb22
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb196
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_pack/version.rb13
-rw-r--r--actionpack/lib/action_view/lookup_context.rb8
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb10
-rw-r--r--actionpack/test/abstract/callbacks_test.rb2
-rw-r--r--actionpack/test/abstract/collector_test.rb2
-rw-r--r--actionpack/test/abstract/layouts_test.rb14
-rw-r--r--actionpack/test/controller/base_test.rb12
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb8
-rw-r--r--actionpack/test/controller/layout_test.rb24
-rw-r--r--actionpack/test/controller/localized_templates_test.rb9
-rw-r--r--actionpack/test/controller/output_escaping_test.rb2
-rw-r--r--actionpack/test/controller/routing_test.rb11
-rw-r--r--actionpack/test/dispatch/cookies_test.rb65
-rw-r--r--actionpack/test/dispatch/mount_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb37
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb8
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb15
-rw-r--r--actionpack/test/template/url_helper_test.rb1
-rw-r--r--activemodel/CHANGELOG.md28
-rw-r--r--activemodel/lib/active_model/errors.rb14
-rw-r--r--activemodel/lib/active_model/secure_password.rb9
-rw-r--r--activemodel/lib/active_model/validations.rb48
-rw-r--r--activemodel/lib/active_model/version.rb13
-rw-r--r--activemodel/test/cases/callbacks_test.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb21
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations_test.rb4
-rw-r--r--activerecord/CHANGELOG.md85
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb6
-rw-r--r--activerecord/lib/active_record/autosave_association.rb14
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb110
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb77
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb52
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb6
-rw-r--r--activerecord/lib/active_record/core.rb28
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/scoping/default.rb8
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb3
-rw-r--r--activerecord/lib/active_record/version.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb19
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb36
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb24
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb20
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb10
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb9
-rw-r--r--activerecord/test/cases/associations/eager_test.rb13
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb25
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb104
-rw-r--r--activerecord/test/cases/batches_test.rb2
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb13
-rw-r--r--activerecord/test/cases/counter_cache_test.rb9
-rw-r--r--activerecord/test/cases/dup_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb9
-rw-r--r--activerecord/test/cases/inheritance_test.rb14
-rw-r--r--activerecord/test/cases/locking_test.rb11
-rw-r--r--activerecord/test/cases/migration/columns_test.rb7
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb4
-rw-r--r--activerecord/test/cases/persistence_test.rb9
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb25
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb18
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations_repair_helper.rb4
-rw-r--r--activerecord/test/fixtures/dog_lovers.yml3
-rw-r--r--activerecord/test/fixtures/dogs.yml1
-rw-r--r--activerecord/test/models/dog.rb5
-rw-r--r--activerecord/test/models/dog_lover.rb5
-rw-r--r--activerecord/test/schema/schema.rb8
-rw-r--r--activesupport/CHANGELOG.md5
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/cache.rb6
-rw-r--r--activesupport/lib/active_support/core_ext.rb4
-rw-r--r--activesupport/lib/active_support/message_verifier.rb6
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb2
-rw-r--r--activesupport/lib/active_support/version.rb13
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb6
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb8
-rw-r--r--activesupport/test/fixtures/xml/jdom_doctype.dtd1
-rw-r--r--activesupport/test/fixtures/xml/jdom_entities.txt1
-rw-r--r--activesupport/test/fixtures/xml/jdom_include.txt1
-rw-r--r--activesupport/test/inflector_test.rb2
-rw-r--r--activesupport/test/transliterate_test.rb2
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb38
-rwxr-xr-xci/travis.rb4
-rw-r--r--guides/code/getting_started/Gemfile2
-rw-r--r--guides/code/getting_started/public/404.html26
-rw-r--r--guides/code/getting_started/public/422.html24
-rw-r--r--guides/code/getting_started/public/500.html26
-rw-r--r--guides/source/2_2_release_notes.md30
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md27
-rw-r--r--guides/source/engines.md9
-rw-r--r--guides/source/form_helpers.md26
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/testing.md35
-rw-r--r--guides/source/upgrading_ruby_on_rails.md33
-rw-r--r--railties/CHANGELOG.md20
-rw-r--r--railties/lib/rails.rb4
-rw-r--r--railties/lib/rails/all.rb4
-rw-r--r--railties/lib/rails/application.rb5
-rw-r--r--railties/lib/rails/commands/application.rb2
-rw-r--r--railties/lib/rails/commands/server.rb3
-rw-r--r--railties/lib/rails/generators/app_base.rb50
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/404.html26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/422.html24
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/500.html26
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb1
-rw-r--r--railties/lib/rails/info.rb4
-rw-r--r--railties/lib/rails/version.rb13
-rw-r--r--railties/test/application/asset_debugging_test.rb2
-rw-r--r--railties/test/application/assets_test.rb6
-rw-r--r--railties/test/application/configuration_test.rb2
-rw-r--r--railties/test/application/console_test.rb12
-rw-r--r--railties/test/application/initializers/i18n_test.rb2
-rw-r--r--railties/test/application/initializers/load_path_test.rb2
-rw-r--r--railties/test/application/middleware/cookies_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb7
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb4
-rw-r--r--railties/test/generators/scaffold_generator_test.rb2
-rw-r--r--railties/test/rails_info_test.rb2
-rw-r--r--tasks/release.rb16
-rw-r--r--version.rb12
190 files changed, 1791 insertions, 792 deletions
diff --git a/.gitignore b/.gitignore
index a3a5304ecd..4c2c8de10c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ debug.log
/.bundle
/.ruby-version
/Gemfile.lock
-/pkg
+pkg
/dist
/doc/rdoc
/*/doc
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index adc5eb6914..6d3cf072c1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,12 +1,14 @@
Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the team](http://contributors.rubyonrails.org)!
-Please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide before submitting any code.
+* If you want to submit a bug report please make sure to follow our [reporting guidelines](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#reporting-an-issue).
+
+* If you want to submit a patch, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide.
*We only accept bug reports and pull requests in GitHub*.
-If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
+* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
-If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
+* If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
Thanks! :heart: :heart: :heart: <br />
Rails Team
diff --git a/Gemfile b/Gemfile
index a367397c89..9abc8a5d66 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,9 +5,10 @@ gemspec
gem 'mocha', '~> 0.13.0', require: false
gem 'rack-cache', '~> 1.2'
gem 'bcrypt-ruby', '~> 3.0.0'
-gem 'jquery-rails', '~> 2.2.0'
+gem 'jquery-rails', github: 'rails/jquery-rails'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0.beta1'
+gem 'thor', github: 'wycats/thor'
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index b065be4922..6f8c79eef2 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -13,7 +13,7 @@ Today is mostly coordination tasks. Here are the things you must do today:
Do not release with a Red CI. You can find the CI status here:
- http://travis-ci.org/#!/rails/rails
+ http://travis-ci.org/rails/rails
=== Is Sam Ruby happy? If not, make him happy.
diff --git a/Rakefile b/Rakefile
index 490627d22c..e577c079df 100644
--- a/Rakefile
+++ b/Rakefile
@@ -141,7 +141,7 @@ task :update_versions do
require File.dirname(__FILE__) + "/version"
File.open("RAILS_VERSION", "w") do |f|
- f.write Rails::VERSION::STRING + "\n"
+ f.puts Rails.version
end
constants = {
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 997046b971..89e31c4be6 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,10 +1,11 @@
module ActionMailer
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActionMailer as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActionMailer.version.segments
+ STRING = ActionMailer.version.to_s
end
end
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index 504ca36483..20b6671283 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -119,7 +119,7 @@ class BaseMailer < ActionMailer::Base
def without_mail_call
end
- def with_nil_as_return_value(hash = {})
+ def with_nil_as_return_value
mail(:template_name => "welcome")
nil
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index b23d0668d3..058a69dcb6 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,50 @@
## Rails 4.0.0 (unreleased) ##
+* Ensure consistent fallback to the default layout lookup for layouts set
+ using symbols or procs that return nil.
+
+ All of the following layouts will result in the default layout lookup:
+
+ layout nil
+
+ layout proc { |c| nil }
+
+ layout :returns_nil
+
+ def returns_nil
+ nil
+ end
+
+ Previously symbols and procs which returned nil resulted in no layout which
+ differed from the `layout nil` behavior.
+
+ *Chris Nicola*
+
+* Create `UpgradeLegacySignedCookieJar` to transparently upgrade existing signed
+ cookies generated by Rails 3.x to avoid invalidating them when upgrading to Rails 4.x.
+
+ *Trevor Turk + Neeraj Singh*
+
+* Raise an `ArgumentError` when a clashing named route is defined.
+
+ *Trevor Turk*
+
+* Allow default url options to accept host with protocol such as `http://`
+
+ config.action_mailer.default_url_options = { host: "http://mydomain.com" }
+
+ *Richard Schneeman*
+
+* Ensure that digest authentication responds with a 401 status when a basic
+ header is received.
+
+ *Brad Dunbar*
+
+* Include I18n locale fallbacks in view lookup.
+ Fixes GH#3512.
+
+ *Juan Barreneche*
+
* Integration and functional tests allow headers and rack env
variables to be passed when performing requests.
Fixes #6513.
@@ -79,6 +124,7 @@
*Thierry Zires*
+
## Rails 4.0.0.beta1 (February 25, 2013) ##
* Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 03eeb841ee..cc8351a489 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -26,5 +26,5 @@ Gem::Specification.new do |s|
s.add_dependency 'erubis', '~> 2.7.0'
s.add_development_dependency 'activemodel', version
- s.add_development_dependency 'tzinfo', '~> 0.3.33'
+ s.add_development_dependency 'tzinfo', '~> 0.3.37'
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 91864f2a35..bac994496e 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -285,10 +285,9 @@ module AbstractController
remove_possible_method(:_layout)
prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
+ default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super"
name_clause = if name
- <<-RUBY
- lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
- RUBY
+ default_behavior
else
<<-RUBY
super
@@ -301,6 +300,7 @@ module AbstractController
when Symbol
<<-RUBY
#{_layout}.tap do |layout|
+ return #{default_behavior} if layout.nil?
unless layout.is_a?(String) || !layout
raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
"should have returned a String, false, or nil"
@@ -308,8 +308,12 @@ module AbstractController
end
RUBY
when Proc
- define_method :_layout_from_proc, &_layout
- _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)"
+ define_method :_layout_from_proc, &_layout
+ <<-RUBY
+ result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
+ return #{default_behavior} if result.nil?
+ result
+ RUBY
when false
nil
when true
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
index 2aa6b7adaf..af36ffa240 100644
--- a/actionpack/lib/action_controller/metal/hide_actions.rb
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
@@ -27,7 +27,7 @@ module ActionController
end
def visible_action?(action_name)
- action_methods.include?(action_name)
+ not hidden_actions.include?(action_name)
end
# Overrides AbstractController::Base#action_methods to remove any methods
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index e295002b16..c7bb2dd147 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -299,6 +299,7 @@ module ActionController
# allow a user to use new nonce without prompting user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
+ return false if value.nil?
t = ::Base64.decode64(value).split(":").first.to_i
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index acad8a0799..23d70c9ea2 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/array/wrap'
require 'active_support/rescuable'
require 'action_dispatch/http/upload'
+require 'stringio'
module ActionController
# Raised when a required parameter is missing.
@@ -68,6 +69,8 @@ module ActionController
# ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
# in test and development environments, +false+ otherwise.
#
+ # Examples:
+ #
# params = ActionController::Parameters.new
# params.permitted? # => false
#
@@ -418,7 +421,7 @@ module ActionController
# Declaration { comment_ids: [] }.
array_of_permitted_scalars_filter(params, key)
else
- # Declaration { user: :name } or { user: [:name, :age, { adress: ... }] }.
+ # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
if element.is_a?(Hash)
element = self.class.new(element) unless element.respond_to?(:permit)
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 0d6015d993..f9b278349e 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -92,7 +92,7 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
- SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
+ SPECIAL_KEYS = %w[extras no-cache max-age public must-revalidate]
def cache_control_segments
if cache_control = self[CACHE_CONTROL]
@@ -108,7 +108,7 @@ module ActionDispatch
cache_control_segments.each do |segment|
directive, argument = segment.split('=', 2)
- if SPESHUL_KEYS.include? directive
+ if SPECIAL_KEYS.include? directive
key = directive.tr('-', '_')
cache_control[key.to_sym] = argument || true
else
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index aff2172788..ebd87c40b5 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -158,29 +158,27 @@ module ActionDispatch
# Returns the +String+ full path including params of the last URL requested.
#
- # app.get "/articles"
- # app.request.fullpath # => "/articles"
+ # # get "/articles"
+ # request.fullpath # => "/articles"
#
- # app.get "/articles?page=2"
- # app.request.fullpath # => "/articles?page=2"
+ # # get "/articles?page=2"
+ # request.fullpath # => "/articles?page=2"
def fullpath
@fullpath ||= super
end
- # Returns the original request URL as a +String+
+ # Returns the original request URL as a +String+.
#
- # app.get "/articles?page=2"
- # app.request.original_url
- # # => "http://www.example.com/articles?page=2"
+ # # get "/articles?page=2"
+ # request.original_url # => "http://www.example.com/articles?page=2"
def original_url
base_url + original_fullpath
end
- # The +String+ MIME type of the request
+ # The +String+ MIME type of the request.
#
- # app.get "/articles"
- # app.request.media_type
- # # => "application/x-www-form-urlencoded"
+ # # get "/articles"
+ # request.media_type # => "application/x-www-form-urlencoded"
def media_type
content_mime_type.to_s
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 97ac462411..ab5399c8ea 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -59,8 +59,9 @@ module ActionDispatch
result = ""
unless options[:only_path]
+ protocol = extract_protocol(options)
unless options[:protocol] == false
- result << (options[:protocol] || "http")
+ result << protocol
result << ":" unless result.match(%r{:|//})
end
result << "//" unless result.match("//")
@@ -83,6 +84,16 @@ module ActionDispatch
end
end
+ # Extracts protocol http:// or https:// from options[:host]
+ # needs to be called whether the :protocol is being used or not
+ def extract_protocol(options)
+ if options[:host] && match = options[:host].match(/(^.*:\/\/)(.*)/)
+ options[:protocol] ||= match[1]
+ options[:host] = match[2]
+ end
+ options[:protocol] || "http"
+ end
+
def host_or_subdomain_and_domain(options)
return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 36a0db6e61..f21d1d4ee5 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
require 'active_support/message_verifier'
@@ -86,7 +87,8 @@ module ActionDispatch
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
- TOKEN_KEY = "action_dispatch.secret_token".freeze
+ SECRET_TOKEN = "action_dispatch.secret_token".freeze
+ SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -94,8 +96,68 @@ module ActionDispatch
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
+ # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
+ module ChainedCookieJars
+ # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
+ #
+ # cookies.permanent[:prefers_open_id] = true
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ #
+ # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
+ #
+ # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
+ #
+ # cookies.permanent.signed[:remember_me] = current_user.id
+ # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ def permanent
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ end
+
+ # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
+ # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
+ # cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.signed[:discount] = 45
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
+ #
+ # cookies.signed[:discount] # => 45
+ def signed
+ @signed ||= begin
+ if @options[:upgrade_legacy_signed_cookie_jar]
+ UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
+ else
+ SignedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+ end
+
+ # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this
+ def signed_using_old_secret #:nodoc:
+ @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:secret_token]), @options)
+ end
+
+ # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
+ # If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.encrypted[:discount] = 45
+ # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ #
+ # cookies.encrypted[:discount] # => 45
+ def encrypted
+ @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+
class CookieJar #:nodoc:
- include Enumerable
+ include Enumerable, ChainedCookieJars
# This regular expression is used to split the levels of a domain.
# The top level domain can be any string without a period or
@@ -115,7 +177,10 @@ module ActionDispatch
{ signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
- token_key: env[TOKEN_KEY] }
+ secret_token: env[SECRET_TOKEN],
+ secret_key_base: env[SECRET_KEY_BASE],
+ upgrade_legacy_signed_cookie_jar: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ }
end
def self.build(request)
@@ -232,59 +297,6 @@ module ActionDispatch
@cookies.each_key{ |k| delete(k, options) }
end
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
- #
- # cookies.permanent[:prefers_open_id] = true
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- #
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
- #
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
- #
- # cookies.permanent.signed[:remember_me] = current_user.id
- # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
- # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
- # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
- # be raised.
- #
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
- #
- # Example:
- #
- # cookies.signed[:discount] = 45
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
- #
- # cookies.signed[:discount] # => 45
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
-
- # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this
- def signed_using_old_secret #:nodoc:
- @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:token_key]), @options)
- end
-
- # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
- # If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception
- # will be raised.
- #
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
- #
- # Example:
- #
- # cookies.encrypted[:discount] = 45
- # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
- #
- # cookies.encrypted[:discount] # => 45
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
-
def write(headers)
@set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
@@ -306,6 +318,8 @@ module ActionDispatch
end
class PermanentCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@key_generator = key_generator
@@ -326,26 +340,11 @@ module ActionDispatch
options[:expires] = 20.years.from_now
@parent_jar[key] = options
end
-
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
-
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
-
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
- end
end
class SignedCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
@@ -372,26 +371,42 @@ module ActionDispatch
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[key] = options
end
+ end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
+ # config.secret_token and config.secret_key_base are both set. It reads
+ # legacy cookies signed with the old dummy key generator and re-saves
+ # them using the new key generator to provide a smooth upgrade path.
+ class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
+ def initialize(*args)
+ super
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
end
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ def [](name)
+ if signed_message = @parent_jar[name]
+ verify_signed_message(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ end
end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
+ def verify_signed_message(signed_message)
+ @verifier.verify(signed_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
end
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
+ def verify_and_upgrade_legacy_signed_message(name, signed_message)
+ @legacy_verifier.verify(signed_message).tap do |value|
+ self[name] = value
+ end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
end
end
class EncryptedCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::DummyKeyGenerator === key_generator
raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
@@ -425,23 +440,6 @@ module ActionDispatch
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[key] = options
end
-
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
-
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
-
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
- end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 93a2b52996..8879291dbd 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -101,7 +101,7 @@ module ActionDispatch
(([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
- (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
+ (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
(([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
)$)
}x
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 619dd22ec1..7fb4719fa0 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -403,11 +403,19 @@ module ActionDispatch
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
+ if name && named_routes[name]
+ raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \
+ "You may have defined two routes with the same name using the `:as` option, or "
+ "you may be overriding a route already defined by a resource with the same naming. " \
+ "For the latter, you can restrict the routes created with `resources` as explained here: \n" \
+ "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
+ end
+
path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
- named_routes[name] = route if name && !named_routes[name]
+ named_routes[name] = route if name
route
end
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 5c87a9cd7c..b5e47d78d1 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,10 +1,11 @@
module ActionPack
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActionPack as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActionPack.version.segments
+ STRING = ActionPack.version.to_s
end
end
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 4e4816d983..d61cc0f304 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -43,7 +43,13 @@ module ActionView
module Accessors #:nodoc:
end
- register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq }
+ register_detail(:locale) do
+ locales = [I18n.locale]
+ locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
+ locales << I18n.default_locale
+ locales.uniq!
+ locales
+ end
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
register_detail(:handlers){ Template::Handlers.extensions }
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
index 6b4ececda2..30b6b8b141 100644
--- a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
@@ -77,7 +77,7 @@ module HTML
# A regular expression of the valid characters used to separate protocols like
# the ':' in 'http://foo.com'
- self.protocol_separator = /:|(&#0*58)|(&#x70)|(%|&#37;)3A/
+ self.protocol_separator = /:|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i
# Specifies a Set of HTML attributes that can have URIs.
self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
@@ -121,8 +121,8 @@ module HTML
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
# gauntlet
- if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
- style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/
+ if style !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ ||
+ style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/
return ''
end
@@ -133,7 +133,7 @@ module HTML
elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
unless val.split().any? do |keyword|
!allowed_css_keywords.include?(keyword) &&
- keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
+ keyword !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/
end
clean << prop + ': ' + val + ';'
end
@@ -182,7 +182,7 @@ module HTML
def contains_bad_protocols?(attr_name, value)
uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
end
end
end
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 1090af3060..8cba049485 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -259,7 +259,7 @@ module AbstractController
end
class TestCallbacksWithArgs < ActiveSupport::TestCase
- test "callbacks still work when invoking process with multiple args" do
+ test "callbacks still work when invoking process with multiple arguments" do
controller = CallbacksWithArgs.new
controller.process(:index, " Howdy!")
assert_equal "Hello world Howdy!", controller.response_body
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index c14d24905b..5709ad0378 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -42,7 +42,7 @@ module AbstractController
end
end
- test "generated methods call custom with args received" do
+ test "generated methods call custom with arguments received" do
collector = MyCollector.new
collector.html
collector.text(:foo)
diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb
index 558a45b87f..70688d7267 100644
--- a/actionpack/test/abstract/layouts_test.rb
+++ b/actionpack/test/abstract/layouts_test.rb
@@ -79,6 +79,14 @@ module AbstractControllerTests
end
end
+ class WithProcReturningNil < Base
+ layout proc { |c| nil }
+
+ def index
+ render template: ActionView::Template::Text.new("Hello nil!")
+ end
+ end
+
class WithZeroArityProc < Base
layout proc { "overwrite" }
@@ -249,6 +257,12 @@ module AbstractControllerTests
assert_equal "Overwrite Hello proc!", controller.response_body
end
+ test "when layout is specified as a proc and the proc retuns nil, don't use a layout" do
+ controller = WithProcReturningNil.new
+ controller.process(:index)
+ assert_equal "Hello nil!", controller.response_body
+ end
+
test "when layout is specified as a proc without parameters it works just the same" do
controller = WithZeroArityProc.new
controller.process(:index)
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 9b42e7631f..d4f18d55a6 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -68,6 +68,12 @@ class RecordIdentifierWithoutDeprecationController < ActionController::Base
include ActionView::RecordIdentifier
end
+class ActionMissingController < ActionController::Base
+ def action_missing(action)
+ render :text => "Response for #{action}"
+ end
+end
+
class ControllerClassTests < ActiveSupport::TestCase
def test_controller_path
@@ -186,6 +192,12 @@ class PerformActionTest < ActionController::TestCase
assert_raise(AbstractController::ActionNotFound) { get :hidden_action }
assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action }
end
+
+ def test_action_missing_should_work
+ use_controller ActionMissingController
+ get :arbitrary_action
+ assert_equal "Response for arbitrary_action", @response.body
+ end
end
class UrlOptionsTest < ActionController::TestCase
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 537de7a2dd..4287856550 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -249,6 +249,14 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
assert_equal 'Definitely Maybe', @response.body
end
+ test "when sent a basic auth header, returns Unauthorized" do
+ @request.env['HTTP_AUTHORIZATION'] = 'Basic Gwf2aXq8ZLF3Hxq='
+
+ get :display
+
+ assert_response :unauthorized
+ end
+
private
def encode_credentials(options)
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 71bcfd664e..a61a1fe99d 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -94,6 +94,18 @@ class HasOwnLayoutController < LayoutTest
layout 'item'
end
+class HasNilLayoutSymbol < LayoutTest
+ layout :nilz
+
+ def nilz
+ nil
+ end
+end
+
+class HasNilLayoutProc < LayoutTest
+ layout proc { |c| nil }
+end
+
class PrependsViewPathController < LayoutTest
def hello
prepend_view_path File.dirname(__FILE__) + '/../fixtures/layout_tests/alt/'
@@ -142,6 +154,18 @@ class LayoutSetInResponseTest < ActionController::TestCase
assert_template :layout => "layouts/item"
end
+ def test_layout_symbol_set_in_controller_returning_nil_falls_back_to_default
+ @controller = HasNilLayoutSymbol.new
+ get :hello
+ assert_template layout: "layouts/layout_test"
+ end
+
+ def test_layout_proc_set_in_controller_returning_nil_falls_back_to_default
+ @controller = HasNilLayoutProc.new
+ get :hello
+ assert_template layout: "layouts/layout_test"
+ end
+
def test_layout_only_exception_when_included
@controller = OnlyLayoutController.new
get :hello
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index bac1d02977..6b02eedaed 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -25,4 +25,13 @@ class LocalizedTemplatesTest < ActionController::TestCase
ensure
I18n.locale = old_locale
end
+
+ def test_use_fallback_locales
+ I18n.locale = :"de-AT"
+ I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.fallbacks[:"de-AT"] = [:de]
+
+ get :hello_world
+ assert_equal "Gutten Tag", @response.body
+ end
end
diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb
index 43a8c05cda..c3c549fbfc 100644
--- a/actionpack/test/controller/output_escaping_test.rb
+++ b/actionpack/test/controller/output_escaping_test.rb
@@ -11,8 +11,6 @@ class OutputEscapingTest < ActiveSupport::TestCase
end
test "escapeHTML shouldn't touch explicitly safe strings" do
- # TODO this seems easier to compose and reason about, but
- # this should be verified
assert_equal "<", ERB::Util.h("<".html_safe)
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 93e94f0f48..978c5aa7ac 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -908,12 +908,13 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal set.routes.first, set.named_routes[:hello]
end
- def test_earlier_named_routes_take_precedence
- set.draw do
- get '/hello/world' => 'a#b', :as => 'hello'
- get '/hello' => 'a#b', :as => 'hello'
+ def test_duplicate_named_route_raises_rather_than_pick_precedence
+ assert_raise ArgumentError do
+ set.draw do
+ get '/hello/world' => 'a#b', :as => 'hello'
+ get '/hello' => 'a#b', :as => 'hello'
+ end
end
- assert_equal set.routes.first, set.named_routes[:hello]
end
def setup_named_route_test
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 5ada5a7603..c532e0b8cc 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -1,6 +1,15 @@
require 'abstract_unit'
+
+begin
+ require 'openssl'
+ OpenSSL::PKCS5
+rescue LoadError, NameError
+ $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
+else
+
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
+require 'active_support/message_verifier'
class CookiesTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -67,6 +76,11 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ def get_signed_cookie
+ cookies.signed[:user_id]
+ head :ok
+ end
+
def set_encrypted_cookie
cookies.encrypted[:foo] = 'bar'
head :ok
@@ -421,6 +435,55 @@ class CookiesTest < ActionController::TestCase
}
end
+ def test_signed_uses_signed_cookie_jar_if_only_secret_token_is_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = nil
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
+ end
+
+ def test_signed_uses_signed_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
+ end
+
+ def test_signed_uses_upgrade_legacy_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacySignedCookieJar, cookies.signed
+ end
+
+ def test_legacy_signed_cookie_is_read_and_transparently_upgraded_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_signed_cookie_is_nil_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.headers["Cookie"] = "user_id=45"
+ get :get_signed_cookie
+
+ assert_equal nil, @controller.send(:cookies).signed[:user_id]
+ assert_equal nil, @response.cookies["user_id"]
+ end
+
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success
@@ -669,3 +732,5 @@ class CookiesTest < ActionController::TestCase
end
end
end
+
+end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 3b008fdff0..e5e28c28be 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -21,7 +21,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
mount SprocketsApp, :at => "/sprockets"
mount SprocketsApp => "/shorthand"
- mount FakeEngine, :at => "/fakeengine"
+ mount FakeEngine, :at => "/fakeengine", :as => :fake
mount FakeEngine, :at => "/getfake", :via => :get
scope "/its_a" do
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 2bf7056ff7..df359ba77d 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2577,22 +2577,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_raises(ActionController::UrlGenerationError){ list_todo_path(:list_id => '2', :id => '1') }
end
- def test_named_routes_collision_is_avoided_unless_explicitly_given_as
- draw do
- scope :as => "routes" do
- get "/c/:id", :as => :collision, :to => "collision#show"
- get "/collision", :to => "collision#show"
- get "/no_collision", :to => "collision#show", :as => nil
-
- get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
- get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
- end
- end
-
- assert_equal "/c/1", routes_collision_path(1)
- assert_equal "/fc/1", routes_forced_collision_path(1)
- end
-
def test_redirect_argument_error
routes = Class.new { include ActionDispatch::Routing::Redirection }.new
assert_raises(ArgumentError) { routes.redirect Object.new }
@@ -2604,9 +2588,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/c/:id", :as => :collision, :to => "collision#show"
get "/collision", :to => "collision#show"
get "/no_collision", :to => "collision#show", :as => nil
-
- get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
- get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
end
end
@@ -2657,6 +2638,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_duplicate_route_name_raises_error
+ assert_raise(ArgumentError) do
+ draw do
+ get '/collision', :to => 'collision#show', :as => 'collision'
+ get '/duplicate', :to => 'duplicate#show', :as => 'collision'
+ end
+ end
+ end
+
+ def test_duplicate_route_name_via_resources_raises_error
+ assert_raise(ArgumentError) do
+ draw do
+ resources :collisions
+ get '/collision', :to => 'collision#show', :as => 'collision'
+ end
+ end
+ end
+
def test_nested_route_in_nested_resource
draw do
resources :posts, :only => [:index, :show] do
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index e56e8ddc57..4123529092 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -48,6 +48,14 @@ module TestUrlGeneration
https!
assert_equal "http://www.example.com/foo", foo_url(:protocol => "http")
end
+
+ test "extracting protocol from host when protocol not present" do
+ assert_equal "httpz://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: nil)
+ end
+
+ test "formatting host when protocol is present" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: "http://")
+ end
end
end
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index d9b57776c9..b1c1b83807 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -200,6 +200,7 @@ class SanitizerTest < ActionController::TestCase
%(<IMG SRC="jav&#x0A;ascript:alert('XSS');">),
%(<IMG SRC="jav&#x0D;ascript:alert('XSS');">),
%(<IMG SRC=" &#14; javascript:alert('XSS');">),
+ %(<IMG SRC="javascript&#x3a;alert('XSS');">),
%(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each_with_index do |img_hack, i|
define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do
assert_sanitized img_hack, "<img>"
@@ -279,6 +280,11 @@ class SanitizerTest < ActionController::TestCase
assert_equal '', sanitize_css(raw)
end
+ def test_should_sanitize_across_newlines
+ raw = %(\nwidth:\nexpression(alert('XSS'));\n)
+ assert_equal '', sanitize_css(raw)
+ end
+
def test_should_sanitize_img_vbscript
assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), '<img />'
end
@@ -299,6 +305,15 @@ class SanitizerTest < ActionController::TestCase
assert_sanitized "<span class=\"\\", "<span class=\"\\\">"
end
+ def test_x03a
+ assert_sanitized %(<a href="javascript&#x3a;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="javascript&#x003a;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="http&#x3a;//legit">), %(<a href="http://legit">)
+ assert_sanitized %(<a href="javascript&#x3A;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="javascript&#x003A;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="http&#x3A;//legit">), %(<a href="http://legit">)
+ end
+
protected
def assert_sanitized(input, expected = nil)
@sanitizer ||= HTML::WhiteListSanitizer.new
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 5d87c96605..e359f47975 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -51,7 +51,6 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
- # TODO: missing test cases
def test_button_to_with_straight_url
assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com")
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 6ba0c7cd6b..e4c80b1bf8 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,31 @@
## Rails 4.0.0 (unreleased) ##
+* Add ActiveModel::Errors#full_messages_for, a method that returns all the error
+ messages for a given attribute.
+
+ *Volodymyr Shatsky*
+
+* Added a method so that validations can be easily cleared on a model.
+ For example:
+
+ class Person
+ include ActiveModel::Validations
+
+ validates_uniqueness_of :first_name
+ validate :cannot_be_robot
+
+ def cannot_be_robot
+ errors.add(:base, 'A person cannot be a robot') if person_is_robot
+ end
+ end
+
+ Now, if someone runs `Person.clear_validators!`, then the following occurs:
+
+ Person.validators # => []
+ Person._validate_callbacks.empty? # => true
+
+ *John Wang*
+
* `has_secure_password` does not fail the confirmation validation
when assigning empty String to `password` and `password_confirmation`.
@@ -70,7 +96,7 @@
*Yves Senn*
-* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_pasword`.
+* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_password`.
*Brian Cardarella + Jeremy Kemper + Trevor Turk*
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 963e52bff3..485eb1a40a 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -352,6 +352,20 @@ module ActiveModel
map { |attribute, message| full_message(attribute, message) }
end
+ # Returns all the full error messages for a given attribute in an array.
+ #
+ # class Person
+ # validates_presence_of :name, :email
+ # validates_length_of :name, in: 5..30
+ # end
+ #
+ # person = Person.create()
+ # person.errors.full_messages_for(:name)
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
+ def full_messages_for(attribute)
+ (get(attribute) || []).map { |message| full_message(attribute, message) }
+ end
+
# Returns a full message for a given attribute.
#
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 9324a1ad0a..474cb0aea0 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -43,8 +43,13 @@ module ActiveModel
# Load bcrypt-ruby only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
- gem 'bcrypt-ruby', '~> 3.0.0'
- require 'bcrypt'
+ begin
+ gem 'bcrypt-ruby', '~> 3.0.0'
+ require 'bcrypt'
+ rescue LoadError
+ $stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install"
+ raise
+ end
attr_reader :password
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 2db4a25f61..714cc95b9a 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -169,6 +169,49 @@ module ActiveModel
_validators.values.flatten.uniq
end
+ # Clears all of the validators and validations.
+ #
+ # Note that this will clear anything that is being used to validate
+ # the model for both the +validates_with+ and +validate+ methods.
+ # It clears the validators that are created with an invocation of
+ # +validates_with+ and the callbacks that are set by an invocation
+ # of +validate+.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # validates_with MyValidator
+ # validates_with OtherValidator, on: :create
+ # validates_with StrictValidator, strict: true
+ # validate :cannot_be_robot
+ #
+ # def cannot_be_robot
+ # errors.add(:base, 'A person cannot be a robot') if person_is_robot
+ # end
+ # end
+ #
+ # Person.validators
+ # # => [
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
+ # # ]
+ #
+ # If one runs Person.clear_validators! and then checks to see what
+ # validators this class has, you would obtain:
+ #
+ # Person.validators # => []
+ #
+ # Also, the callback set by +validate :cannot_be_robot+ will be erased
+ # so that:
+ #
+ # Person._validate_callbacks.empty? # => true
+ #
+ def clear_validators!
+ reset_callbacks(:validate)
+ _validators.clear
+ end
+
# List all validators that are being used to validate a specific attribute.
#
# class Person
@@ -333,7 +376,4 @@ module ActiveModel
end
end
-Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
- filename = File.basename(path)
- require "active_model/validations/#{filename}"
-end
+Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 7586b02037..35c4b30999 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,10 +1,11 @@
module ActiveModel
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActiveModel as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveModel.version.segments
+ STRING = ActiveModel.version.to_s
end
end
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
index 086e7266ff..c4c34b0be7 100644
--- a/activemodel/test/cases/callbacks_test.rb
+++ b/activemodel/test/cases/callbacks_test.rb
@@ -107,7 +107,7 @@ class CallbacksTest < ActiveModel::TestCase
test "after_create callbacks with both callbacks declared in one line" do
assert_equal ["callback1", "callback2"], Violin1.new.create.history
end
- test "after_create callbacks with both callbacks declared in differnt lines" do
+ test "after_create callbacks with both callbacks declared in different lines" do
assert_equal ["callback1", "callback2"], Violin2.new.create.history
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 51dcfc37d8..139fb6795a 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -216,6 +216,26 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages
end
+
+ test 'full_messages_for should contain all the messages for a given attribute' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages_for(:name)
+ end
+
+ test 'full_messages_for should not contain messages for another attributes' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:email, "can not be blank")
+ assert_equal ["name can not be blank"], person.errors.full_messages_for(:name)
+ end
+
+ test "full_messages_for should return an empty array in case if errors hash doesn't contain a given attribute" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert_equal [], person.errors.full_messages_for(:email)
+ end
test 'full_message should return the given message if attribute equals :base' do
person = Person.new
@@ -225,6 +245,7 @@ class ErrorsTest < ActiveModel::TestCase
test 'full_message should return the given message with the attribute name included' do
person = Person.new
assert_equal "name can not be blank", person.errors.full_message(:name, "can not be blank")
+ assert_equal "name_test can not be blank", person.errors.full_message(:name_test, "can not be blank")
end
test 'should return a JSON hash representation of the errors' do
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 457f553661..daa7a8d5c4 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -50,7 +50,7 @@ class ValidatesWithTest < ActiveModel::TestCase
end
end
- test "vaidation with class that adds errors" do
+ test "validation with class that adds errors" do
Topic.validates_with(ValidatorThatAddsErrors)
topic = Topic.new
assert topic.invalid?, "A class that adds errors causes the record to be invalid"
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index d3723fa45b..2934e70c2f 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -26,11 +26,11 @@ class ValidationsTest < ActiveModel::TestCase
def test_single_field_validation
r = Reply.new
r.title = "There's no content!"
- assert r.invalid?, "A reply without content shouldn't be saveable"
+ assert r.invalid?, "A reply without content shouldn't be savable"
assert r.after_validation_performed, "after_validation callback should be called"
r.content = "Messa content!"
- assert r.valid?, "A reply with content should be saveable"
+ assert r.valid?, "A reply with content should be savable"
assert r.after_validation_performed, "after_validation callback should be called"
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index e5ab6bac58..3fe8d7ec2e 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,62 @@
## Rails 4.0.0 (unreleased) ##
+* Allow ActiveRecord::Base.connection_handler to have thread affinity and be
+ settable, this effectively allows ActiveRecord to be used in a multi threaded
+ setup with multiple connections to multiple dbs.
+
+ *Sam Saffron*
+
+* `rename_column` preserves auto_increment in mysql migrations.
+ Fixes #3493.
+
+ *Vipul A M*
+
+* PostgreSQL geometric type point is supported by ActiveRecord. Fixes #7324.
+
+ *Martin Schuerrer*
+
+* Add suport for concurrent indexing in PostgreSQL adapter via the
+ `algorithm: :concurrently` option
+
+ add_index(:people, :last_name, algorithm: :concurrently)
+
+ Also adds support for MySQL index algorithms (`COPY`, `INPLACE`,
+ `DEFAULT`) via the `algorithm: :copy` option
+
+ add_index(:people, :last_name, algorithm: :copy) # or :inplace/:default
+
+ *Dan McClain*
+
+* Add an `add_index` override in Postgresql adapter and MySQL adapter
+ to allow custom index type support. Fixes #6101.
+
+ add_index(:wikis, :body, :using => 'gin')
+
+ *Stefan Huber* and *Doabit*
+
+* After extraction of mass-assignment attributes (which protects [id, type]
+ by default) we can pass id to `update_attributes` and it will update
+ another record because id will be used in where statement. We never have
+ to change id in where statement because we try to set/replace fields for
+ already loaded record but we have to try to set new id for that record.
+
+ *Dmitry Vorotilin*
+
+* Models with multiple counter cache associations now update correctly on destroy.
+ See #7706.
+
+ *Ian Young*
+
+* If `:inverse_of` is true on an association, then when one calls `find()` on
+ the association, Active Record will first look through the in-memory objects
+ in the association for a particular id. Then, it will go to the DB if it
+ is not found. This is accomplished by calling `find_by_scan` in
+ collection associations whenever `options[:inverse_of]` is not nil.
+
+ Fixes #9470.
+
+ *John Wang*
+
* `rake db:create` does not change permissions of the MySQL root user.
Fixes #8079.
@@ -81,11 +138,11 @@
*Yves Senn*
-* Fix quoting for sqlite migrations using copy_table_contents() with binary
+* Fix quoting for sqlite migrations using `copy_table_contents` with binary
columns.
These would fail with "SQLite3::SQLException: unrecognized token" because
- the column was not being passed to quote() so the data was not quoted
+ the column was not being passed to `quote` so the data was not quoted
correctly.
*Matthew M. Boedicker*
@@ -258,7 +315,7 @@
*John Wang*
-* Postgresql timestamp with time zone (timestamptz) datatype now returns a
+* PostgreSQL timestamp with time zone (timestamptz) datatype now returns a
ActiveSupport::TimeWithZone instance instead of a string
*Troy Kruthoff*
@@ -488,7 +545,7 @@
*James Miller*
-* Allow store accessors to be overrided like other attribute methods, e.g.:
+* Allow store accessors to be overridden like other attribute methods, e.g.:
class User < ActiveRecord::Base
store :settings, accessors: [ :color, :homepage ], coder: JSON
@@ -511,11 +568,11 @@
*Dylan Smith*
* Schema dumper supports dumping the enabled database extensions to `schema.rb`
- (currently only supported by postgresql).
+ (currently only supported by PostgreSQL).
*Justin George*
-* The database adpters now converts the options passed thought `DATABASE_URL`
+* The database adapters 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:
@@ -614,7 +671,7 @@
*John Wang*
-* Collection associations `#empty?` always respects builded records.
+* Collection associations `#empty?` always respects built records.
Fixes #8879.
Example:
@@ -675,7 +732,7 @@
*Yves Senn*
-* Add ability for postgresql adapter to disable user triggers in `disable_referential_integrity`.
+* Add ability for PostgreSQL adapter to disable user triggers in `disable_referential_integrity`.
Fixes #5523.
*Gary S. Weaver*
@@ -799,14 +856,14 @@
*Carlos Antonio da Silva*
-* Fix postgresql adapter to handle BC timestamps correctly
+* Fix PostgreSQL adapter to handle BC timestamps correctly
HistoryEvent.create!(name: "something", occured_at: Date.new(0) - 5.years)
*Bogdan Gusiev*
-* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is silently dropped.
- Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits on these types.
+* When running migrations on PostgreSQL, the `:limit` option for `binary` and `text` columns is silently dropped.
+ Previously, these migrations caused sql exceptions, because PostgreSQL doesn't support limits on these types.
*Victor Costan*
@@ -1438,7 +1495,7 @@
*Egor Lynko*
-* Added support for specifying the precision of a timestamp in the postgresql
+* Added support for specifying the precision of a timestamp in the PostgreSQL
adapter. So, instead of having to incorrectly specify the precision using the
`:limit` option, you may use `:precision`, as intended. For example, in a migration:
@@ -1493,7 +1550,7 @@
* Move HABTM validity checks to `ActiveRecord::Reflection`. One side effect of
this is to move when the exceptions are raised from the point of declaration
- to when the association is built. This is consistant with other association
+ to when the association is built. This is consistent with other association
validity checks.
*Andrew White*
@@ -1706,7 +1763,7 @@
* Added the schema cache dump feature.
- `Schema cache dump` feature was implemetend. This feature can dump/load internal state of `SchemaCache` instance
+ `Schema cache dump` feature was implemented. This feature can dump/load internal state of `SchemaCache` instance
because we want to boot rails more quickly when we have many models.
Usage notes:
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 0c670bdaa1..eb08f72286 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -966,7 +966,7 @@ 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> and <tt>destory_all</tt> will always call the <tt>destroy</tt> method of the
+ # For +has_many+, <tt>destroy</tt> and <tt>destroy_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.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 868095f068..4c4b0f08e5 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -203,7 +203,7 @@ module ActiveRecord
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
# the kind of the class of the associated objects. Meant to be used as
# a sanity check when you are about to assign an associated record.
- def raise_on_type_mismatch(record)
+ def raise_on_type_mismatch!(record)
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 54b1a69774..8eec4f56af 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -8,7 +8,7 @@ module ActiveRecord
end
def replace(record)
- raise_on_type_mismatch(record) if record
+ raise_on_type_mismatch!(record) if record
update_counters(record)
replace_keys(record)
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 88ce03a3cd..eae5eed3a1 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
reflection.polymorphic_inverse_of(record.class)
end
- def raise_on_type_mismatch(record)
+ def raise_on_type_mismatch!(record)
# A polymorphic association cannot have a type mismatch, by definition
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index fbcb21118d..9ac561b997 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -31,7 +31,7 @@ module ActiveRecord::Associations::Builder
end
def belongs_to_counter_cache_before_destroy_for_#{name}
- unless marked_for_destruction?
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
record = #{name}
record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 906560bd44..2385c90c1a 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -79,7 +79,7 @@ module ActiveRecord
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
else
- if options[:finder_sql]
+ if options[:finder_sql] || options[:inverse_of]
find_by_scan(*args)
else
scope.find(*args)
@@ -318,7 +318,7 @@ module ActiveRecord
# Replace this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
def replace(other_array)
- other_array.each { |val| raise_on_type_mismatch(val) }
+ other_array.each { |val| raise_on_type_mismatch!(val) }
original_target = load_target.dup
if owner.new_record?
@@ -465,7 +465,7 @@ module ActiveRecord
def delete_or_destroy(records, method)
records = records.flatten
- records.each { |record| raise_on_type_mismatch(record) }
+ records.each { |record| raise_on_type_mismatch!(record) }
existing_records = records.reject { |r| r.new_record? }
if existing_records.empty?
@@ -506,9 +506,9 @@ module ActiveRecord
result = true
records.flatten.each do |record|
- raise_on_type_mismatch(record)
- add_to_target(record) do |r|
- result &&= insert_record(record) unless owner.new_record?
+ raise_on_type_mismatch!(record)
+ add_to_target(record) do |rec|
+ result &&= insert_record(rec) unless owner.new_record?
end
end
@@ -567,7 +567,8 @@ module ActiveRecord
end
end
- # If using a custom finder_sql, #find scans the entire collection.
+ # If using a custom finder_sql or if the :inverse_of option has been
+ # specified, then #find scans the entire collection.
def find_by_scan(*args)
expects_array = args.first.kind_of?(Array)
ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index b7b4d7e3ae..29fae809da 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
else
if options[:dependent] == :destroy
# No point in executing the counter update since we're going to destroy the parent anyway
- load_target.each(&:mark_for_destruction)
+ load_target.each { |t| t.destroyed_by_association = reflection }
destroy_all
else
delete_all
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index d1458f30ba..a74dd1cdab 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -29,7 +29,7 @@ module ActiveRecord
def concat(*records)
unless owner.new_record?
records.flatten.each do |record|
- raise_on_type_mismatch(record)
+ raise_on_type_mismatch!(record)
record.save! if record.new_record?
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index ee816d2392..98bd010f70 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
def replace(record, save = true)
- raise_on_type_mismatch(record) if record
+ raise_on_type_mismatch!(record) if record
load_target
# If target and record are nil, or target is equal to record,
diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
index 8e8925f0a9..9a3fada380 100644
--- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
@@ -35,7 +35,7 @@ module ActiveRecord
# record
def associated_records_by_owner
records = {}
- super.each do |owner_key, rows|
+ super.each_value do |rows|
rows.map! { |row| records[row[klass.primary_key]] ||= klass.instantiate(row) }
end
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index ecfa556ab4..e536f5ebcc 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -81,7 +81,7 @@ module ActiveRecord
end
def extract_callstack_for_multiparameter_attributes(pairs)
- attributes = { }
+ attributes = {}
pairs.each do |(multiparameter_name, value)|
attribute_name = multiparameter_name.split("(").first
@@ -146,7 +146,7 @@ module ActiveRecord
end
else
# else column is a timestamp, so if Date bits were not provided, error
- validate_missing_parameters!([1,2,3])
+ validate_required_parameters!([1,2,3])
# If Date bits were provided but blank, then return nil
return if blank_date_parameter?
@@ -172,14 +172,14 @@ module ActiveRecord
def read_other(klass)
max_position = extract_max_param
positions = (1..max_position)
- validate_missing_parameters!(positions)
+ validate_required_parameters!(positions)
set_values = values.values_at(*positions)
klass.new(*set_values)
end
# Checks whether some blank date parameter exists. Note that this is different
- # than the validate_missing_parameters! method, since it just checks for blank
+ # than the validate_required_parameters! method, since it just checks for blank
# positions instead of missing ones, and does not raise in case one blank position
# exists. The caller is responsible to handle the case of this returning true.
def blank_date_parameter?
@@ -187,7 +187,7 @@ module ActiveRecord
end
# If some position is not provided, it errors out a missing parameter exception.
- def validate_missing_parameters!(positions)
+ def validate_required_parameters!(positions)
if missing_parameter = positions.detect { |position| !values.key?(position) }
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e0bfdb8f3e..22405c5d74 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods # :nodoc:
- # Use a mutex; we don't want two thread simaltaneously trying to define
+ # Use a mutex; we don't want two thread simultaneously trying to define
# attribute methods.
@attribute_methods_mutex.synchronize do
return if attribute_methods_generated?
@@ -334,7 +334,7 @@ module ActiveRecord
private
# Returns a Hash of the Arel::Attributes and attribute values that have been
- # type casted for use in an Arel insert/update method.
+ # typecasted for use in an Arel insert/update method.
def arel_attributes_with_values(attribute_names)
attrs = {}
arel_table = self.class.arel_table
@@ -348,7 +348,7 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
attribute_names.select do |name|
- column_for_attribute(name) && !pk_attribute?(name) && !readonly_attribute?(name)
+ column_for_attribute(name) && !readonly_attribute?(name)
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 55542262b0..0df3e57947 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -212,6 +212,7 @@ module ActiveRecord
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
def reload(options = nil)
@marked_for_destruction = false
+ @destroyed_by_association = nil
super
end
@@ -231,6 +232,19 @@ module ActiveRecord
@marked_for_destruction
end
+ # Records the association that is being destroyed and destroying this
+ # record in the process.
+ def destroyed_by_association=(reflection)
+ @destroyed_by_association = reflection
+ end
+
+ # Returns the association for the parent being destroyed.
+ #
+ # Used to avoid updating the counter cache unnecessarily.
+ def destroyed_by_association
+ @destroyed_by_association
+ end
+
# Returns whether or not this record has been changed in any way (including whether
# any of its nested autosave associations are likewise changed)
def changed_for_autosave?
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index f6cdc67b4d..8d22942a06 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -3,7 +3,6 @@ require 'yaml'
module ActiveRecord
module Coders # :nodoc:
class YAMLColumn # :nodoc:
- RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ]
attr_accessor :object_class
@@ -34,7 +33,7 @@ module ActiveRecord
obj ||= object_class.new if object_class != Object
obj
- rescue *RESCUE_ERRORS
+ rescue ArgumentError, Psych::SyntaxError
yaml
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 9137504d15..bf2f945448 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -238,7 +238,7 @@ module ActiveRecord
@checkout_timeout = spec.config[:checkout_timeout] || 5
@dead_connection_timeout = spec.config[:dead_connection_timeout] || 5
- @reaper = Reaper.new(self, spec.config[:reaping_frequency] || 10)
+ @reaper = Reaper.new self, spec.config[:reaping_frequency]
@reaper.run
# default max pool size to 5
@@ -406,7 +406,9 @@ module ActiveRecord
synchronize do
stale = Time.now - @dead_connection_timeout
connections.dup.each do |conn|
- remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
+ if conn.in_use? && stale > conn.last_use && !conn.active?
+ remove conn
+ end
end
end
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 2859fb31e8..c0a2111571 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# 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.
+ # operations to use prefixes in temporary operations.
def allowed_index_name_length
index_name_length
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 902dbd148e..2f17e47b7c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -8,41 +8,21 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :using) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc:
def string_to_binary(value)
value
end
- def sql_type
- base.type_to_sql(type.to_sym, limit, precision, scale)
- end
-
def primary_key?
- type.to_sym == :primary_key
- end
-
- def to_sql
- column_sql = "#{base.quote_column_name(name)} #{sql_type}"
- column_options = {}
- column_options[:null] = null unless null.nil?
- column_options[:default] = default unless default.nil?
- column_options[:column] = self
- add_column_options!(column_sql, column_options) unless primary_key?
- column_sql
+ primary_key || type.to_sym == :primary_key
end
-
- private
-
- def add_column_options!(sql, options)
- base.add_column_options!(sql, options)
- end
end
# Represents the schema of an SQL table in an abstract way. This class
@@ -68,19 +48,25 @@ module ActiveRecord
class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
- attr_accessor :columns, :indexes
+ attr_accessor :indexes
+ attr_reader :name, :temporary, :options
- def initialize(base)
- @columns = []
+ def initialize(types, name, temporary, options)
@columns_hash = {}
@indexes = {}
- @base = base
+ @native = types
+ @temporary = temporary
+ @options = options
+ @name = name
end
+ def columns; @columns_hash.values; end
+
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
- def primary_key(name)
- column(name, :primary_key)
+ def primary_key(name, type = :primary_key, options = {})
+ options[:primary_key] = true
+ column(name, type, options)
end
# Returns a ColumnDefinition for the column with name +name+.
@@ -233,20 +219,14 @@ module ActiveRecord
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
end
- column = self[name] || new_column_definition(@base, name, type)
-
- limit = options.fetch(:limit) do
- native[type][:limit] if native[type].is_a?(Hash)
- end
-
- column.limit = limit
- column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
+ @columns_hash[name] = new_column_definition(name, type, options)
self
end
+ def remove_column(name)
+ @columns_hash.delete name.to_s
+ end
+
[:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
define_method column_type do |*args|
options = args.extract_options!
@@ -283,23 +263,26 @@ module ActiveRecord
end
alias :belongs_to :references
- # Returns a String whose contents are the column definitions
- # concatenated together. This string can then be prepended and appended to
- # to generate the final SQL to create the table.
- def to_sql
- columns.map { |c| c.to_sql } * ', '
- end
+ def new_column_definition(name, type, options) # :nodoc:
+ column = create_column_definition name, type
+ limit = options.fetch(:limit) do
+ native[type][:limit] if native[type].is_a?(Hash)
+ end
- private
- def create_column_definition(base, name, type)
- ColumnDefinition.new base, name, type
+ column.limit = limit
+ column.precision = options[:precision]
+ column.scale = options[:scale]
+ column.default = options[:default]
+ column.null = options[:null]
+ column.first = options[:first]
+ column.after = options[:after]
+ column.primary_key = type == :primary_key || options[:primary_key]
+ column
end
- def new_column_definition(base, name, type)
- definition = create_column_definition base, name, type
- @columns << definition
- @columns_hash[name] = definition
- definition
+ private
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
def primary_key_column_name
@@ -308,7 +291,24 @@ module ActiveRecord
end
def native
- @base.native_database_types
+ @native
+ end
+ end
+
+ class AlterTable # :nodoc:
+ attr_reader :adds
+
+ def initialize(td)
+ @td = td
+ @adds = []
+ end
+
+ def name; @td.name; end
+
+ def add_column(name, type, options)
+ name = name.to_s
+ type = type.to_sym
+ @adds << @td.new_column_definition(name, type, options)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index f587bf8140..cdf0cbe218 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -5,7 +5,7 @@ module ActiveRecord
# The goal of this module is to move Adapter specific column
# definitions to the Adapter instead of having it in the schema
# dumper itself. This code represents the normal case.
- # We can then redefine how certain data types may be handled in the schema dumper on the
+ # We can then redefine how certain data types may be handled in the schema dumper on the
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column, types)
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 cd4409295f..e95e97e4a8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -171,14 +171,14 @@ module ActiveRecord
#
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
- td = create_table_definition
+ td = create_table_definition table_name, options[:temporary], options[:options]
unless options[:id] == false
pk = options.fetch(:primary_key) {
Base.get_primary_key table_name.to_s.singularize
}
- td.primary_key pk
+ td.primary_key pk, options.fetch(:id, :primary_key), options
end
yield td if block_given?
@@ -187,11 +187,7 @@ module ActiveRecord
drop_table(table_name, options)
end
- create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
- create_sql << "#{quote_table_name(table_name)} ("
- create_sql << td.to_sql
- create_sql << ") #{options[:options]}"
- execute create_sql
+ execute schema_creation.accept td
td.indexes.each_pair { |c,o| add_index table_name, c, o }
end
@@ -359,9 +355,9 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- execute(add_column_sql)
+ at = create_alter_table table_name
+ at.add_column(column_name, type, options)
+ execute schema_creation.accept at
end
# Removes the given columns from the table definition.
@@ -501,7 +497,14 @@ module ActiveRecord
#
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
- # Note: only supported by PostgreSQL.
+ # ====== Creating an index with a specific method
+ # add_index(:developers, :name, :using => 'btree')
+ # generates
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
+ #
+ # Note: only supported by PostgreSQL and MySQL
+ #
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -685,6 +688,9 @@ module ActiveRecord
if options[:null] == false
sql << " NOT NULL"
end
+ if options[:auto_increment] == true
+ sql << " AUTO_INCREMENT"
+ end
end
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
@@ -749,12 +755,20 @@ 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, :internal)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm)
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 index_algorithms.key?(options[:algorithm])
+ algorithm = index_algorithms[options[:algorithm]]
+ elsif options[:algorithm].present?
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+ end
+
+ using = "USING #{options[:using]}" if options[:using].present?
+
if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
@@ -769,6 +783,7 @@ module ActiveRecord
index_type = options
max_index_length = allowed_index_name_length
+ algorithm = using = nil
end
if index_name.length > max_index_length
@@ -779,7 +794,7 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options]
+ [index_name, index_type, index_columns, index_options, algorithm, using]
end
def index_name_for_remove(table_name, options = {})
@@ -829,8 +844,12 @@ module ActiveRecord
end
private
- def create_table_definition
- TableDefinition.new(self)
+ def create_table_definition(name, temporary, options)
+ TableDefinition.new native_database_types, name, temporary, options
+ end
+
+ def create_alter_table(name)
+ AlterTable.new create_table_definition(name, false, {})
end
def update_table_definition(table_name, base)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 73c80a3220..2b6685499a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -78,7 +78,7 @@ module ActiveRecord
@joinable = options.fetch(:joinable, true)
end
- # This state is necesarry so that we correctly handle stuff that might
+ # This state is necessary so that we correctly handle stuff that might
# happen in a commit/rollback. But it's kinda distasteful. Maybe we can
# find a better way to structure it in the future.
def finishing?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 7949bcb5ce..1915c444ef 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -18,6 +18,7 @@ module ActiveRecord
autoload :ColumnDefinition
autoload :TableDefinition
autoload :Table
+ autoload :AlterTable
end
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
@@ -100,6 +101,75 @@ module ActiveRecord
@visitor = nil
end
+ class SchemaCreation
+ def initialize(conn)
+ @conn = conn
+ @cache = {}
+ end
+
+ def accept(o)
+ m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}"
+ send m, o
+ end
+
+ private
+
+ def visit_AlterTable(o)
+ sql = "ALTER TABLE #{quote_table_name(o.name)} "
+ sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
+ end
+
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
+ def visit_ColumnDefinition(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ column_sql = "#{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(column_sql, column_options(o)) unless o.primary_key?
+ column_sql
+ end
+
+ def visit_TableDefinition(o)
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
+ create_sql << "#{quote_table_name(o.name)} ("
+ create_sql << o.columns.map { |c| accept c }.join(', ')
+ create_sql << ") #{o.options}"
+ create_sql
+ end
+
+ def column_options(o)
+ column_options = {}
+ column_options[:null] = o.null unless o.null.nil?
+ column_options[:default] = o.default unless o.default.nil?
+ column_options[:column] = o
+ column_options
+ end
+
+ def quote_column_name(name)
+ @conn.quote_column_name name
+ end
+
+ def quote_table_name(name)
+ @conn.quote_table_name name
+ end
+
+ def type_to_sql(type, limit, precision, scale)
+ @conn.type_to_sql type.to_sym, limit, precision, scale
+ end
+
+ def add_column_options!(column_sql, column_options)
+ @conn.add_column_options! column_sql, column_options
+ column_sql
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
def lease
synchronize do
unless in_use
@@ -212,6 +282,13 @@ module ActiveRecord
[]
end
+ # A list of index algorithms, to be filled by adapters that
+ # support them. MySQL and PostgreSQL has support for them right
+ # now.
+ def index_algorithms
+ {}
+ end
+
# QUOTING ==================================================
# Returns a bind substitution value given a +column+ and list of current
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 f88f5742a8..cc5e6ac44d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -3,13 +3,34 @@ require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+
+ def visit_AddColumn(o)
+ add_column_position!(super, o)
+ end
+
+ def add_column_position!(sql, column)
+ if column.first
+ sql << " FIRST"
+ elsif column.after
+ sql << " AFTER #{quote_column_name(column.after)}"
+ end
+ sql
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
- attr_reader :collation, :strict
+ attr_reader :collation, :strict, :extra
- def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false)
+ def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
@strict = strict
@collation = collation
-
+ @extra = extra
super(name, default, sql_type, null)
end
@@ -187,6 +208,10 @@ module ActiveRecord
NATIVE_DATABASE_TYPES
end
+ def index_algorithms
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
+ end
+
# HELPER METHODS ===========================================
# The two drivers have slightly different ways of yielding hashes of results, so
@@ -196,8 +221,8 @@ module ActiveRecord
end
# Overridden by the adapters to instantiate their specific Column type.
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, extra)
end
# Must return the Mysql error number from the exception, if the exception has an
@@ -411,6 +436,7 @@ module ActiveRecord
next if row[:Key_name] == 'PRIMARY' # skip the primary key
current_index = row[:Key_name]
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
+ indexes.last.using = row[:Index_type].downcase.to_sym
end
indexes.last.columns << row[:Column_name]
@@ -426,7 +452,7 @@ module ActiveRecord
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
end
end
end
@@ -459,10 +485,6 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- def add_column(table_name, column_name, type, options = {})
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
- end
-
def change_column_default(table_name, column_name, default)
column = column_for(table_name, column_name)
change_column table_name, column_name, column.sql_type, :default => default
@@ -487,6 +509,11 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def add_index(table_name, column_name, options = {}) #:nodoc:
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
+ end
+
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
case type.to_s
@@ -572,6 +599,10 @@ module ActiveRecord
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
+
protected
# MySQL is too stupid to create a temporary table for use subquery, so we have
@@ -651,6 +682,7 @@ module ActiveRecord
if column = columns(table_name).find { |c| c.name == column_name.to_s }
options[:default] = column.default
options[:null] = column.null
+ options[:auto_increment] = (column.extra == "auto_increment")
else
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 25b8aef617..6b319fed88 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -63,8 +63,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
end
def error_number(exception)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 7544c2a783..0e01fe0fdb 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -150,8 +150,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
end
def error_number(exception) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 3d8f0b575c..14ef07a75e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -2,6 +2,17 @@ module ActiveRecord
module ConnectionAdapters
class PostgreSQLColumn < Column
module Cast
+ def point_to_string(point)
+ "(#{point[0]},#{point[1]})"
+ end
+
+ def string_to_point(string)
+ if string[0] == '(' && string[-1] == ')'
+ string = string[1...-1]
+ end
+ string.split(',').map{ |v| Float(v) }
+ end
+
def string_to_time(string)
return string unless String === string
@@ -30,8 +41,8 @@ module ActiveRecord
nil
elsif String === string
Hash[string.scan(HstorePair).map { |k,v|
- v = v.upcase == 'NULL' ? nil : v.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
- k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
+ k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
[k,v]
}]
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 68f2f2ca7b..51f377dfd7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -63,6 +63,16 @@ module ActiveRecord
end
end
+ class Point < Type
+ def type_cast(value)
+ if String === value
+ ConnectionAdapters::PostgreSQLColumn.string_to_point value
+ else
+ value
+ end
+ end
+ end
+
class Array < Type
attr_reader :subtype
def initialize(subtype)
@@ -330,6 +340,7 @@ module ActiveRecord
register_type 'time', OID::Time.new
register_type 'path', OID::Identity.new
+ register_type 'point', OID::Point.new
register_type 'polygon', OID::Identity.new
register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 43f991b362..6329733abc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -18,27 +18,33 @@ module ActiveRecord
def quote(value, column = nil) #:nodoc:
return super unless column
+ sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale)
+
case value
when Range
- if /range$/ =~ column.sql_type
- "'#{PostgreSQLColumn.range_to_string(value)}'::#{column.sql_type}"
+ if /range$/ =~ sql_type
+ "'#{PostgreSQLColumn.range_to_string(value)}'::#{sql_type}"
else
super
end
when Array
- if column.array
- "'#{PostgreSQLColumn.array_to_string(value, column, self)}'"
+ case sql_type
+ when 'point' then super(PostgreSQLColumn.point_to_string(value))
else
- super
+ if column.array
+ "'#{PostgreSQLColumn.array_to_string(value, column, self)}'"
+ else
+ super
+ end
end
when Hash
- case column.sql_type
+ case sql_type
when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
when IPAddr
- case column.sql_type
+ case sql_type
when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
else super
end
@@ -51,14 +57,14 @@ module ActiveRecord
super
end
when Numeric
- if column.sql_type == 'money' || [:string, :text].include?(column.type)
+ if sql_type == 'money' || [:string, :text].include?(column.type)
# Not truly string input, so doesn't require (or allow) escape string syntax.
"'#{value}'"
else
super
end
when String
- case column.sql_type
+ case sql_type
when 'bytea' then "'#{escape_bytea(value)}'"
when 'xml' then "xml '#{quote_string(value)}'"
when /^bit/
@@ -90,8 +96,12 @@ module ActiveRecord
super(value, column)
end
when Array
- return super(value, column) unless column.array
- PostgreSQLColumn.array_to_string(value, column, self)
+ case column.sql_type
+ when 'point' then PostgreSQLColumn.point_to_string(value)
+ else
+ return super(value, column) unless column.array
+ PostgreSQLColumn.array_to_string(value, column, self)
+ end
when String
return super(value, column) unless 'bytea' == column.sql_type
{ :value => value, :format => 1 }
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 3bc61c5e0c..0e1afbae8d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,6 +1,42 @@
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
+ def visit_ColumnDefinition(o)
+ sql = super
+ if o.primary_key? && o.type == :uuid
+ sql << " PRIMARY KEY "
+ add_column_options!(sql, column_options(o))
+ end
+ sql
+ end
+
+ def add_column_options!(sql, options)
+ if options[:array] || options[:column].try(:array)
+ sql << '[]'
+ end
+
+ column = options.fetch(:column) { return super }
+ if column.type == :uuid && options[:default] =~ /\(\)/
+ sql << " DEFAULT #{options[:default]}"
+ else
+ super
+ end
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
module SchemaStatements
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
@@ -124,8 +160,9 @@ module ActiveRecord
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
where = inddef.scan(/WHERE (.+)$/).flatten[0]
+ type = inddef.scan(/USING (.+?) /).flatten[0].to_sym
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, type)
end.compact
end
@@ -337,10 +374,7 @@ module ActiveRecord
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
clear_cache!
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
-
- execute add_column_sql
+ super
end
# Changes the column of a table.
@@ -375,6 +409,11 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def add_index(table_name, column_name, options = {}) #:nodoc:
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ end
+
def remove_index!(table_name, index_name) #:nodoc:
execute "DROP INDEX #{quote_table_name(index_name)}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index dfa4c3967a..46e41e6b48 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -330,6 +330,13 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+ def primary_key(name, type = :primary_key, options = {})
+ return super unless type == :uuid
+ options[:default] ||= 'uuid_generate_v4()'
+ options[:primary_key] = true
+ column name, type, options
+ end
+
def column(name, type = nil, options = {})
super
column = self[name]
@@ -344,8 +351,8 @@ module ActiveRecord
private
- def create_column_definition(base, name, type)
- ColumnDefinition.new base, name, type
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
end
@@ -426,6 +433,10 @@ module ActiveRecord
true
end
+ def index_algorithms
+ { concurrently: 'CONCURRENTLY' }
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -519,8 +530,7 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.query 'SELECT 1'
- true
+ @connection.connect_poll != PG::PGRES_POLLING_FAILED
rescue PGError
false
end
@@ -628,19 +638,6 @@ module ActiveRecord
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
end
- def add_column_options!(sql, options)
- if options[:array] || options[:column].try(:array)
- sql << '[]'
- end
-
- column = options.fetch(:column) { return super }
- if column.type == :uuid && options[:default] =~ /\(\)/
- sql << " DEFAULT #{options[:default]}"
- else
- super
- end
- end
-
# Set the authorized user for this session
def session_auth=(user)
clear_cache!
@@ -670,6 +667,10 @@ module ActiveRecord
@use_insert_returning
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
@@ -714,7 +715,14 @@ module ActiveRecord
# populate composite types
nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ if OID.registered_type? row['typname']
+ # this composite type is explicitly registered
+ vector = OID::NAMES[row['typname']]
+ else
+ # use the default for composite types
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ end
+
OID::TYPE_MAP[row['oid'].to_i] = vector
end
@@ -901,8 +909,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition
- TableDefinition.new(self)
+ def create_table_definition(name, temporary, options)
+ TableDefinition.new native_database_types, name, temporary, options
end
def update_table_definition(table_name, base)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d3ffee3a8b..c3bebf7a5e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -458,7 +458,7 @@ module ActiveRecord
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
alter_table(table_name) do |definition|
- definition.columns.delete(definition[column_name])
+ definition.remove_column column_name
end
end
@@ -499,6 +499,10 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def valid_type?(type)
+ true
+ end
+
protected
def select(sql, name = nil, binds = []) #:nodoc:
exec_query(sql, name, binds)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index aa56219755..1ed05c1bd4 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -77,8 +77,17 @@ module ActiveRecord
mattr_accessor :disable_implicit_join_references, instance_writer: false
self.disable_implicit_join_references = false
- class_attribute :connection_handler, instance_writer: false
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+ class_attribute :default_connection_handler, instance_writer: false
+
+ def self.connection_handler
+ Thread.current[:active_record_connection_handler] || self.default_connection_handler
+ end
+
+ def self.connection_handler=(handler)
+ Thread.current[:active_record_connection_handler] = handler
+ end
+
+ self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
end
module ClassMethods
@@ -176,6 +185,7 @@ module ActiveRecord
@columns_hash = self.class.column_types.dup
init_internals
+ init_changed_attributes
ensure_proper_type
populate_with_current_scope_attributes
@@ -246,9 +256,7 @@ module ActiveRecord
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@changed_attributes = {}
- self.class.column_defaults.each do |attr, orig_value|
- @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
- end
+ init_changed_attributes
@aggregation_cache = {}
@association_cache = {}
@@ -427,11 +435,21 @@ module ActiveRecord
@readonly = false
@destroyed = false
@marked_for_destruction = false
+ @destroyed_by_association = nil
@new_record = true
@txn = nil
@_start_transaction_state = {}
@transaction_state = nil
@reflects_state = [false]
end
+
+ def init_changed_attributes
+ # Intentionally avoid using #column_defaults since overriden defaults (as is done in
+ # optimistic locking) won't get written unless they get marked as changed
+ self.class.columns.each do |c|
+ attr, orig_value = c.name, c.default
+ @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 3135465dfe..15736575a2 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -9,7 +9,7 @@ module ActiveRecord
yield
return current[:available_queries_for_explain]
ensure
- # Note that the return value above does not depend on this assigment.
+ # Note that the return value above does not depend on this assignment.
current[:available_queries_for_explain] = original
end
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index 11b53275e1..fbd7a4d891 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -24,7 +24,6 @@ module ActiveRecord
rows.each(&block)
end
- RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ] # :nodoc:
private
def rows
@@ -32,7 +31,7 @@ module ActiveRecord
begin
data = YAML.load(render(IO.read(@file)))
- rescue *RESCUE_ERRORS => error
+ rescue ArgumentError, Psych::SyntaxError => error
raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
end
@rows = data ? validate(data).to_a : []
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 2958d08210..c26fc76515 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -752,7 +752,7 @@ module ActiveRecord
def fixtures(*fixture_set_names)
if fixture_set_names.first == :all
fixture_set_names = Dir["#{fixture_path}/**/*.{yml}"]
- fixture_set_names.map! { |f| f[(fixture_path.size + 1)..-5] }
+ fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
else
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 5d7762ec3a..d3edcf3cdb 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -102,7 +102,7 @@ module ActiveRecord
# table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
# * <tt>change_table(name, options)</tt>: Allows to make column alterations to
- # the table called +name+. It makes the table object availabe to a block that
+ # the table called +name+. It makes the table object available to a block that
# can then add/remove columns, indexes or foreign keys to it.
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
# to +new_name+.
@@ -858,7 +858,7 @@ module ActiveRecord
end
def current_version
- migrated.sort.last || 0
+ migrated.max || 0
end
def current_migration
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b25d0601cb..f881778591 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -443,7 +443,7 @@ module ActiveRecord
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)
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was)).arel.compile_update(bind_attrs)
klass.connection.update stmt, 'SQL', db_columns_with_values
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 037097d2dd..4d24654015 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -603,7 +603,7 @@ module ActiveRecord
"\n" \
" Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
"\n" \
- "Currently, Active Record recognises the table in the string, and knows to JOIN the " \
+ "Currently, Active Record recognizes the table in the string, and knows to JOIN the " \
"comments table to the query, rather than loading comments in a separate query. " \
"However, doing this without writing a full-blown SQL parser is inherently flawed. " \
"Since we don't want to write an SQL parser, we are removing this functionality. " \
@@ -612,7 +612,7 @@ module ActiveRecord
"\n" \
" Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \
"\n" \
- "If you don't rely on implicit join references you can disable the feature entirely" \
+ "If you don't rely on implicit join references you can disable the feature entirely " \
"by setting `config.active_record.disable_implicit_join_references = true`."
)
true
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 14520381c9..e2685d0478 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -37,7 +37,7 @@ module ActiveRecord
end
# Finds the first record matching the specified conditions. There
- # is no implied ording so if order matters, you should specify it
+ # is no implied ordering so if order matters, you should specify it
# yourself.
#
# If no record is found, returns <tt>nil</tt>.
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index bd783a94cf..f44d46d15b 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -48,7 +48,7 @@ module ActiveRecord
column = reflection.foreign_key
end
- queries << build(table[column.to_sym], value)
+ queries << build(table[column], value)
queries
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 10a31109d5..257221174b 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -614,7 +614,7 @@ module ActiveRecord
#
# The returned <tt>ActiveRecord::NullRelation</tt> inherits from Relation and implements the
# Null Object pattern. It is an object with defined null behavior and always returns an empty
- # array of records without quering the database.
+ # array of records without querying the database.
#
# Any subsequent condition chained to the returned relation will continue
# generating an empty relation and will not fire any query to the database.
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index df090b972d..fa9de926c5 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -118,7 +118,7 @@ HEADER
# then dump all non-primary key columns
column_specs = columns.map do |column|
- raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
@connection.column_spec(column, @types)
end.compact
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 5bd481082e..cde4f29d08 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -27,14 +27,6 @@ module ActiveRecord
# Post.unscoped {
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
- #
- # It is recommended that you use the block form of unscoped because
- # chaining unscoped with +scope+ does not work. Assuming that
- # +published+ is a +scope+, the following two statements
- # are equal: the +default_scope+ is applied on both.
- #
- # Post.unscoped.published
- # Post.published
def unscoped
block_given? ? relation.scoping { yield } : relation
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 33718ef0e9..4dbd668fcf 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -160,7 +160,7 @@ module ActiveRecord
# end
# end
#
- # only "Kotori" is created. (This works on MySQL and PostgreSQL, but not on SQLite3.)
+ # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.
#
# Most databases don't support true nested transactions. At the time of
# writing, the only database that we're aware of that supports true nested
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 3706885881..26dca415ff 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -74,8 +74,7 @@ module ActiveRecord
protected
def perform_validations(options={}) # :nodoc:
- perform_validation = options[:validate] != false
- perform_validation ? valid?(options[:context]) : true
+ options[:validate] == false || valid?(options[:context])
end
end
end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index c0471bb506..f2b041ad97 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,10 +1,11 @@
module ActiveRecord
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActiveRecord as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveRecord.version.segments
+ STRING = ActiveRecord.version.to_s
end
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 8812cf1b7d..9050ae3fe3 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -21,20 +21,39 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_name_exists?) do |*|
false
end
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
assert_equal expected, add_index(:people, :last_name, :length => 10)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+
+ %w(btree hash).each do |type|
+ expected = "CREATE INDEX `index_people_on_last_name` USING #{type} ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :using => type)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
+ assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
+ assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :coyp)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?)
end
@@ -70,8 +89,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
def test_add_timestamps
with_real_execute do
begin
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- end
+ ActiveRecord::Base.connection.create_table :delete_me
ActiveRecord::Base.connection.add_timestamps :delete_me
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index b965983fec..1844a2e0dc 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -17,7 +17,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
def test_connect_with_url
- run_without_connection do |orig|
+ run_without_connection do
ar_config = ARTest.connection_config['arunit']
skip "This test doesn't work with custom socket location" if ar_config['socket']
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 0eb1cc511e..a75883cd3a 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -16,6 +16,15 @@ module ActiveRecord
eosql
end
+ def test_valid_column
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
+
+ def test_invalid_column
+ assert_not @conn.valid_type?(:foobar)
+ end
+
def test_client_encoding
assert_equal Encoding::UTF_8, @conn.client_encoding
end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index d94bb629a7..e6e54bf20a 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -35,6 +35,25 @@ module ActiveRecord
def test_table_exists_wrong_schema
assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
end
+
+ def test_dump_indexes
+ index_a_name = 'index_post_title'
+ index_b_name = 'index_post_body'
+
+ table = Post.table_name
+
+ @connection.execute "CREATE INDEX `#{index_a_name}` ON `#{table}` (`title`);"
+ @connection.execute "CREATE INDEX `#{index_b_name}` USING btree ON `#{table}` (`body`(10));"
+
+ indexes = @connection.indexes(table).sort_by {|i| i.name}
+ assert_equal 2,indexes.size
+
+ assert_equal :btree, indexes.select{|i| i.name == index_a_name}[0].using
+ assert_equal :btree, indexes.select{|i| i.name == index_b_name}[0].using
+
+ @connection.execute "DROP INDEX `#{index_a_name}` ON `#{table}`;"
+ @connection.execute "DROP INDEX `#{index_b_name}` ON `#{table}`;"
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index a83399d0cd..48d63aeef5 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -21,20 +21,39 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*|
false
end
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
assert_equal expected, add_index(:people, :last_name, :length => 10)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+
+ %w(btree hash).each do |type|
+ expected = "CREATE INDEX `index_people_on_last_name` USING #{type} ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :using => type)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
+ assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
+ assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :coyp)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?)
end
@@ -70,8 +89,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
def test_add_timestamps
with_real_execute do
begin
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- end
+ ActiveRecord::Base.connection.create_table :delete_me
ActiveRecord::Base.connection.add_timestamps :delete_me
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 94429e772f..78f754d2ce 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -44,6 +44,24 @@ module ActiveRecord
assert_match(/database 'foo-bar'/, e.inspect)
end
+ def test_dump_indexes
+ index_a_name = 'index_post_title'
+ index_b_name = 'index_post_body'
+
+ table = Post.table_name
+
+ @connection.execute "CREATE INDEX `#{index_a_name}` ON `#{table}` (`title`);"
+ @connection.execute "CREATE INDEX `#{index_b_name}` USING btree ON `#{table}` (`body`(10));"
+
+ indexes = @connection.indexes(table).sort_by {|i| i.name}
+ assert_equal 2,indexes.size
+
+ assert_equal :btree, indexes.select{|i| i.name == index_a_name}[0].using
+ assert_equal :btree, indexes.select{|i| i.name == index_b_name}[0].using
+
+ @connection.execute "DROP INDEX `#{index_a_name}` ON `#{table}`;"
+ @connection.execute "DROP INDEX `#{index_b_name}` ON `#{table}`;"
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 01c3e6b49b..16329689c0 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -29,9 +29,29 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
false
end
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
+ expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently)
+
+ %w(gin gist hash btree).each do |type|
+ expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, using: type)
+
+ expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently)
+ end
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :copy)
+ end
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist)
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')
+ assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist)
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index c03660957e..6b726ce875 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -81,42 +81,6 @@ module ActiveRecord
assert_equal 'SCHEMA', @connection.logged[0][1]
end
- def test_reconnection_after_simulated_disconnection_with_verify
- assert @connection.active?
- original_connection_pid = @connection.query('select pg_backend_pid()')
-
- # Fail with bad connection on next query attempt.
- raw_connection = @connection.raw_connection
- raw_connection_class = class << raw_connection ; self ; end
- raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def query_fake(*args)
- if !( @called ||= false )
- self.stubs(:status).returns(PGconn::CONNECTION_BAD)
- @called = true
- raise PGError
- else
- self.unstub(:status)
- query_unfake(*args)
- end
- end
-
- alias query_unfake query
- alias query query_fake
- CODE
-
- begin
- @connection.verify!
- new_connection_pid = @connection.query('select pg_backend_pid()')
- ensure
- raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- alias query query_unfake
- undef query_fake
- CODE
- end
-
- assert_not_equal original_connection_pid, new_connection_pid, "Should have a new underlying connection pid"
- end
-
# Must have with_manual_interventions set to true for this
# test to run.
# When prompted, restart the PostgreSQL server with the
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index ad98d7c8ce..e434b4861c 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -40,25 +40,15 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert @connection.extensions.include?('hstore'), "extension list should include hstore"
end
- def test_hstore_enabled
+ def test_disable_enable_hstore
assert @connection.extension_enabled?('hstore')
- end
-
- def test_disable_hstore
- if @connection.extension_enabled?('hstore')
- @connection.disable_extension 'hstore'
- assert_not @connection.extension_enabled?('hstore')
- end
- end
-
- def test_enable_hstore
- if @connection.extension_enabled?('hstore')
- @connection.disable_extension 'hstore'
- end
-
+ @connection.disable_extension 'hstore'
assert_not @connection.extension_enabled?('hstore')
@connection.enable_extension 'hstore'
assert @connection.extension_enabled?('hstore')
+ ensure
+ # Restore column(s) dropped by `drop extension hstore cascade;`
+ load_schema
end
def test_column
@@ -189,6 +179,10 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_cycle('ca' => 'cà', 'ac' => 'àc')
end
+ def test_multiline
+ assert_cycle("a\nb" => "c\nd")
+ end
+
private
def assert_cycle hash
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 05e0f0e192..17d77c5454 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -10,6 +10,15 @@ module ActiveRecord
@connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))')
end
+ def test_valid_column
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
+ assert @connection.valid_type?(column.type)
+ end
+
+ def test_invalid_column
+ assert_not @connection.valid_type?(:foobar)
+ end
+
def test_primary_key
assert_equal 'id', @connection.primary_key('ex')
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index cd31900d4e..e8dd188ec8 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -11,16 +11,19 @@ class SchemaTest < ActiveRecord::TestCase
INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
INDEX_C_NAME = 'c_index_full_text_search'
INDEX_D_NAME = 'd_index_things_on_description_desc'
+ INDEX_E_NAME = 'e_index_things_on_name_vector'
INDEX_A_COLUMN = 'name'
INDEX_B_COLUMN_S1 = 'email'
INDEX_B_COLUMN_S2 = 'moment'
INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))}
INDEX_D_COLUMN = 'description'
+ INDEX_E_COLUMN = 'name_vector'
COLUMNS = [
'id integer',
'name character varying(50)',
'email character varying(50)',
'description character varying(100)',
+ 'name_vector tsvector',
'moment timestamp without time zone default now()'
]
PK_TABLE_NAME = 'table_with_pk'
@@ -61,6 +64,8 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
+ @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
+ @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
@connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
@@ -236,15 +241,15 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_dump_indexes_for_schema_one
- do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_dump_indexes_for_schema_two
- do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_dump_indexes_for_schema_multiple_schemas_in_search_path
- do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_with_uppercase_index_name
@@ -344,15 +349,20 @@ class SchemaTest < ActiveRecord::TestCase
@connection.schema_search_path = "'$user', public"
end
- def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name)
+ def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)
with_schema_search_path(this_schema_name) do
indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
- assert_equal 3,indexes.size
+ assert_equal 4,indexes.size
do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)
+ do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name)
+ indexes.select{|i| i.name != INDEX_E_NAME}.each do |index|
+ assert_equal :btree, index.using
+ end
+ assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using
assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 53002c5265..c0c0e8898c 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -35,6 +35,16 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
@connection.execute 'drop table if exists pg_uuids'
end
+ def test_id_is_uuid
+ assert_equal :uuid, UUID.columns_hash['id'].type
+ assert UUID.primary_key
+ end
+
+ def test_id_has_a_default
+ u = UUID.create
+ assert_not_nil u.id
+ end
+
def test_auto_create_uuid
u = UUID.create
u.reload
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 21fb111c91..a5a22bc30b 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -90,7 +90,7 @@ protected
end
def table_indexes_without_name(table)
- @connection.indexes('comments_with_index').delete(:name)
+ @connection.indexes(table).delete(:name)
end
def row_count(table)
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 003052bac4..dcbec24acf 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -25,6 +25,15 @@ module ActiveRecord
@conn.intercepted = true
end
+ def test_valid_column
+ column = @conn.columns('items').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
+
+ def test_invalid_column
+ assert @conn.valid_type?(:foobar)
+ end
+
def teardown
@conn.intercepted = false
@conn.logged = []
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 1de7ee0846..d6850215b5 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -193,7 +193,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- def test_finding_with_includes_on_has_one_assocation_with_same_include_includes_only_once
+ def test_finding_with_includes_on_has_one_association_with_same_include_includes_only_once
author = authors(:david)
post = author.post_about_thinking_with_last_comment
last_comment = post.last_comment
@@ -467,7 +467,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts_with_comments = people(:michael).posts.merge(:includes => :comments, :order => 'posts.id').to_a
posts_with_author = people(:michael).posts.merge(:includes => :author, :order => 'posts.id').to_a
posts_with_comments_and_author = people(:michael).posts.merge(:includes => [ :comments, :author ], :order => 'posts.id').to_a
- assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
+ assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum + post.comments.size }
assert_equal authors(:david), assert_no_queries { posts_with_author.first.author }
assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
end
@@ -523,7 +523,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit
posts = Post.all.merge!(:order => 'posts.id asc', :includes => [ :author, :comments ], :limit => 2).to_a
assert_equal 2, posts.size
- assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
+ assert_equal 3, posts.inject(0) { |sum, post| sum + post.comments.size }
end
def test_eager_with_has_many_and_limit_and_conditions
@@ -1174,6 +1174,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" }
end
+ test "preloading a through association twice does not reset it" do
+ members = Member.includes(current_membership: :club).includes(:club).to_a
+ assert_no_queries {
+ assert_equal 3, members.map(&:current_membership).map(&:club).size
+ }
+ end
+
test "works in combination with order(:symbol)" do
author = Author.includes(:posts).references(:posts).order(:name).where('posts.title IS NOT NULL').first
assert_equal authors(:bob), author
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 8c9b4fb921..c8cad84013 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -278,6 +278,31 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert interests[1].man.equal? man
end
+ def test_parent_instance_should_find_child_instance_using_child_instance_id
+ man = Man.create!
+ interest = Interest.create!
+ man.interests = [interest]
+
+ assert interest.equal?(man.interests.first), "The inverse association should use the interest already created and held in memory"
+ assert interest.equal?(man.interests.find(interest.id)), "The inverse association should use the interest already created and held in memory"
+ assert man.equal?(man.interests.first.man), "Two inversion should lead back to the same object that was originally held"
+ assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held"
+ end
+
+ def test_parent_instance_should_find_child_instance_using_child_instance_id_when_created
+ man = Man.create!
+ interest = Interest.create!(man: man)
+
+ assert man.equal?(man.interests.first.man), "Two inverses should lead back to the same object that was originally held"
+ assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held"
+
+ assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed"
+ man.name = "Ben Bitdiddle"
+ assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the parent name is changed"
+ man.interests.find(interest.id).man.name = "Alyssa P. Hacker"
+ assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the child name is changed"
+ end
+
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests }
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index c05481dd91..aabeea025f 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -464,7 +464,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert saved_post.reload.tags(true).include?(new_tag)
- new_post = Post.new(:title => "Association replacmenet works!", :body => "You best believe it.")
+ new_post = Post.new(:title => "Association replacement works!", :body => "You best believe it.")
saved_tag = tags(:general)
new_post.tags << saved_tag
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index c503c21e27..648596b828 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -159,8 +159,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_read_attributes_before_type_cast
- category = Category.new({:name=>"Test categoty", :type => nil})
- category_attrs = {"name"=>"Test categoty", "id" => nil, "type" => nil, "categorizations_count" => nil}
+ category = Category.new({:name=>"Test category", :type => nil})
+ category_attrs = {"name"=>"Test category", "id" => nil, "type" => nil, "categorizations_count" => nil}
assert_equal category_attrs , category.attributes_before_type_cast
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 960c27da3e..69eef500f5 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -141,13 +141,13 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_limit_should_sanitize_sql_injection_for_limit_without_comas
+ def test_limit_should_sanitize_sql_injection_for_limit_without_commas
assert_raises(ArgumentError) do
Topic.limit("1 select * from schema").to_a
end
end
- def test_limit_should_sanitize_sql_injection_for_limit_with_comas
+ def test_limit_should_sanitize_sql_injection_for_limit_with_commas
assert_raises(ArgumentError) do
Topic.limit("1, 7 procedure help()").to_a
end
@@ -307,20 +307,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("last_read", ex.errors[0].attribute)
end
- def test_initialize_abstract_class
- e = assert_raises(NotImplementedError) do
- FirstAbstractClass.new
- end
- assert_equal("FirstAbstractClass is an abstract class and can not be instantiated.", e.message)
- end
-
- def test_initialize_base
- e = assert_raises(NotImplementedError) do
- ActiveRecord::Base.new
- end
- assert_equal("ActiveRecord::Base is an abstract class and can not be instantiated.", e.message)
- end
-
def test_create_after_initialize_without_block
cb = CustomBulb.create(:name => 'Dude')
assert_equal('Dude', cb.name)
@@ -854,7 +840,7 @@ class BasicsTest < ActiveRecord::TestCase
# Reload and check that we have all the geometric attributes.
h = Geometric.find(g.id)
- assert_equal '(5,6.1)', h.a_point
+ assert_equal [5.0, 6.1], h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
@@ -883,7 +869,7 @@ class BasicsTest < ActiveRecord::TestCase
# Reload and check that we have all the geometric attributes.
h = Geometric.find(g.id)
- assert_equal '(5,6.1)', h.a_point
+ assert_equal [5.0, 6.1], h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
@@ -894,6 +880,29 @@ class BasicsTest < ActiveRecord::TestCase
objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
assert_equal true, objs[0].isclosed
+
+ # test native ruby formats when defining the geometric types
+ g = Geometric.new(
+ :a_point => [5.0, 6.1],
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
+ :a_box => '(2.0, 3), (5.5, 7.0)',
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
+ :a_circle => '((5.3, 10.4), 2)'
+ )
+
+ assert g.save
+
+ # Reload and check that we have all the geometric attributes.
+ h = Geometric.find(g.id)
+
+ assert_equal [5.0, 6.1], h.a_point
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
end
end
@@ -1038,7 +1047,7 @@ class BasicsTest < ActiveRecord::TestCase
Joke.reset_sequence_name
end
- def test_dont_clear_inheritnce_column_when_setting_explicitly
+ def test_dont_clear_inheritance_column_when_setting_explicitly
Joke.inheritance_column = "my_type"
before_inherit = Joke.inheritance_column
@@ -1569,4 +1578,61 @@ class BasicsTest < ActiveRecord::TestCase
klass = Class.new(ActiveRecord::Base)
assert_equal ['foo'], klass.all.merge!(select: 'foo').select_values
end
+
+ test "connection_handler can be overriden" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ thread_connection_handler = nil
+
+ t = Thread.new do
+ klass.connection_handler = new_handler
+ thread_connection_handler = klass.connection_handler
+ end
+ t.join
+
+ assert_equal klass.connection_handler, orig_handler
+ assert_equal thread_connection_handler, new_handler
+ end
+
+ test "new threads get default the default connection handler" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ handler = nil
+
+ t = Thread.new do
+ handler = klass.connection_handler
+ end
+ t.join
+
+ assert_equal handler, orig_handler
+ assert_equal klass.connection_handler, orig_handler
+ assert_equal klass.default_connection_handler, orig_handler
+ end
+
+ test "changing a connection handler in a main thread does not poison the other threads" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ after_handler = nil
+ is_set = false
+
+ t = Thread.new do
+ klass.connection_handler = new_handler
+ is_set = true
+ Thread.stop
+ after_handler = klass.connection_handler
+ end
+
+ while(!is_set)
+ Thread.pass
+ end
+
+ klass.connection_handler = orig_handler
+ t.wakeup
+ t.join
+
+ assert_equal after_handler, new_handler
+ assert_equal orig_handler, klass.connection_handler
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index acb8b5f562..87559925e7 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -11,7 +11,7 @@ class EachTest < ActiveRecord::TestCase
Post.count('id') # preheat arel's table cache
end
- def test_each_should_excecute_one_query_per_batch
+ def test_each_should_execute_one_query_per_batch
assert_queries(Post.count + 1) do
Post.find_each(:batch_size => 1) do |post|
assert_kind_of Post, post
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 7457bafd4e..187cad9599 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -520,7 +520,7 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
- def test_inheritence_of_callbacks
+ def test_inheritance_of_callbacks
parent = ParentDeveloper.new
assert !parent.after_save_called
parent.save
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index bd2fbaa7db..dbb2f223cd 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -8,6 +8,7 @@ module ActiveRecord
def @adapter.native_database_types
{:string => "varchar"}
end
+ @viz = @adapter.schema_creation
end
def test_can_set_coder
@@ -35,25 +36,25 @@ module ActiveRecord
def test_should_not_include_default_clause_when_default_is_null
column = Column.new("title", nil, "varchar(20)")
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal "title varchar(20)", column_def.to_sql
+ assert_equal "title varchar(20)", @viz.accept(column_def)
end
def test_should_include_default_clause_when_default_is_present
column = Column.new("title", "Hello", "varchar(20)")
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, column_def.to_sql
+ assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, @viz.accept(column_def)
end
def test_should_specify_not_null_if_null_option_is_false
column = Column.new("title", "Hello", "varchar(20)", false)
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql
+ assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def)
end
if current_adapter?(:MysqlAdapter)
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 7d06fb5093..ac093251a5 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -115,6 +115,15 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test "update other counters on parent destroy" do
+ david, joanna = dog_lovers(:david, :joanna)
+ joanna = joanna # squelch a warning
+
+ assert_difference 'joanna.reload.dogs_count', -1 do
+ david.destroy
+ end
+ end
+
test "reset the right counter if two have the same foreign key" do
michael = people(:michael)
assert_nothing_raised(ActiveRecord::StatementInvalid) do
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index fe105b9d22..dbf6005a67 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -110,7 +110,7 @@ module ActiveRecord
def test_dup_validity_is_independent
repair_validations(Topic) do
Topic.validates_presence_of :title
- topic = Topic.new("title" => "Litterature")
+ topic = Topic.new("title" => "Literature")
topic.valid?
duped = topic.dup
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 8ad40ec3f4..f6cfee0cb8 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -576,6 +576,15 @@ class LoadAllFixturesTest < ActiveRecord::TestCase
end
end
+class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase
+ self.fixture_path = Pathname.new(FIXTURES_ROOT).join('all')
+ fixtures :all
+
+ def test_all_there
+ assert_equal %w(developers people tasks), fixture_table_names.sort
+ end
+end
+
class FasterFixturesTest < ActiveRecord::TestCase
fixtures :categories, :authors
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 0b9ccbf60a..99d54e7526 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -172,6 +172,20 @@ class InheritanceTest < ActiveRecord::TestCase
assert_equal Firm, firm.class
end
+ def test_new_with_abstract_class
+ e = assert_raises(NotImplementedError) do
+ AbstractCompany.new
+ end
+ assert_equal("AbstractCompany is an abstract class and can not be instantiated.", e.message)
+ end
+
+ def test_new_with_ar_base
+ e = assert_raises(NotImplementedError) do
+ ActiveRecord::Base.new
+ end
+ assert_equal("ActiveRecord::Base is an abstract class and can not be instantiated.", e.message)
+ end
+
def test_new_with_invalid_type
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'InvalidType') }
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index a0a3e6cb0d..77891b9156 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -193,11 +193,19 @@ class OptimisticLockingTest < ActiveRecord::TestCase
def test_lock_without_default_sets_version_to_zero
t1 = LockWithoutDefault.new
assert_equal 0, t1.lock_version
+
+ t1.save
+ t1 = LockWithoutDefault.find(t1.id)
+ assert_equal 0, t1.lock_version
end
def test_lock_with_custom_column_without_default_sets_version_to_zero
t1 = LockWithCustomColumnWithoutDefault.new
assert_equal 0, t1.custom_lock_version
+
+ t1.save
+ t1 = LockWithCustomColumnWithoutDefault.find(t1.id)
+ assert_equal 0, t1.custom_lock_version
end
def test_readonly_attributes
@@ -341,9 +349,6 @@ end
# is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
# blocks, so separate script called by Kernel#system is needed.
# (See exec vs. async_exec in the PostgreSQL adapter.)
-
-# TODO: The Sybase, and OpenBase adapters currently have no support for pessimistic locking
-
unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
class PessimisticLockingTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index e52809f0f8..52906c8a01 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -62,6 +62,13 @@ module ActiveRecord
assert_equal 70000, default_after
end
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ def test_mysql_rename_column_preserves_auto_increment
+ rename_column "test_models", "id", "id_test"
+ assert_equal "auto_increment", connection.columns("test_models").find { |c| c.name == "id_test" }.extra
+ end
+ end
+
def test_rename_nonexistent_column
exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
ActiveRecord::StatementInvalid
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 94837341fc..b6e140b912 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -167,7 +167,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record
Man.accepts_nested_attributes_for(:interests)
man = Man.create(:name => "John")
- interest = man.interests.create :topic => 'gardning'
+ interest = man.interests.create :topic => 'gardening'
man = Man.find man.id
man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}]
assert_equal man.interests.first.topic, man.interests[0].topic
@@ -806,7 +806,7 @@ module NestedAttributesOnACollectionAssociationTests
assert_nothing_raised(NoMethodError) { @pirate.save! }
end
- def test_numeric_colum_changes_from_zero_to_no_empty_string
+ def test_numeric_column_changes_from_zero_to_no_empty_string
Man.accepts_nested_attributes_for(:interests)
repair_validations(Interest) do
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b936cca875..a29189df05 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -661,6 +661,15 @@ class PersistencesTest < ActiveRecord::TestCase
topic.reload
assert !topic.approved?
assert_equal "The First Topic", topic.title
+
+ assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
+ topic.update_attributes(id: 3, title: "Hm is it possible?")
+ end
+ assert_not_equal "Hm is it possible?", Topic.find(3).title
+
+ topic.update_attributes(id: 1234)
+ assert_nothing_raised { topic.reload }
+ assert_equal topic.title, Topic.find(1234).title
end
def test_update!
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index 8ce44636b4..92d1e013e8 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -6,26 +6,31 @@ module ActiveRecord
class WhereChainTest < ActiveRecord::TestCase
fixtures :posts
+ def setup
+ super
+ @name = 'title'
+ end
+
def test_not_eq
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'hello')
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello')
relation = Post.where.not(title: 'hello')
assert_equal([expected], relation.where_values)
end
def test_not_null
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], nil)
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], nil)
relation = Post.where.not(title: nil)
assert_equal([expected], relation.where_values)
end
def test_not_in
- expected = Arel::Nodes::NotIn.new(Post.arel_table[:title], %w[hello goodbye])
+ expected = Arel::Nodes::NotIn.new(Post.arel_table[@name], %w[hello goodbye])
relation = Post.where.not(title: %w[hello goodbye])
assert_equal([expected], relation.where_values)
end
def test_association_not_eq
- expected = Arel::Nodes::NotEqual.new(Comment.arel_table[:title], 'hello')
+ expected = Arel::Nodes::NotEqual.new(Comment.arel_table[@name], 'hello')
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
assert_equal(expected.to_sql, relation.where_values.first.to_sql)
end
@@ -33,20 +38,20 @@ module ActiveRecord
def test_not_eq_with_preceding_where
relation = Post.where(title: 'hello').where.not(title: 'world')
- expected = Arel::Nodes::Equality.new(Post.arel_table[:title], 'hello')
+ expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'hello')
assert_equal(expected, relation.where_values.first)
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'world')
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'world')
assert_equal(expected, relation.where_values.last)
end
def test_not_eq_with_succeeding_where
relation = Post.where.not(title: 'hello').where(title: 'world')
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'hello')
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello')
assert_equal(expected, relation.where_values.first)
- expected = Arel::Nodes::Equality.new(Post.arel_table[:title], 'world')
+ expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'world')
assert_equal(expected, relation.where_values.last)
end
@@ -65,10 +70,10 @@ module ActiveRecord
def test_chaining_multiple
relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
- expected = Arel::Nodes::NotIn.new(Post.arel_table[:author_id], [1, 2])
+ expected = Arel::Nodes::NotIn.new(Post.arel_table['author_id'], [1, 2])
assert_equal(expected, relation.where_values[0])
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'ruby on rails')
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'ruby on rails')
assert_equal(expected, relation.where_values[1])
end
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 2b4aadc7ed..6cd89b6227 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -390,20 +390,20 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scope_with_inheritance
wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash
- assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal "Jamis", wheres['name']
+ assert_equal 50000, wheres['salary']
end
def test_default_scope_with_module_includes
wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash
- assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal "Jamis", wheres['name']
+ assert_equal 50000, wheres['salary']
end
def test_default_scope_with_multiple_calls
wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash
- assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal "Jamis", wheres['name']
+ assert_equal 50000, wheres['salary']
end
def test_scope_overwrites_default
@@ -563,7 +563,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
Developer.select("id").unscope("select")
end
- assert_raises(ArgumentError) do
+ assert_raises(ArgumentError) do
Developer.select("id").unscope(5)
end
end
@@ -634,7 +634,11 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
+
+ assert_equal 11, DeveloperCalledJamis.unscoped.length
+ assert_equal 1, DeveloperCalledJamis.poor.length
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
+ assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length
end
def test_default_scope_select_ignored_by_aggregations
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 29b45944aa..57457359b1 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -54,7 +54,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert !t2.save, "Shouldn't save t2 as unique"
assert_equal ["has already been taken"], t2.errors[:title]
- t2.title = "Now Im really also unique"
+ t2.title = "Now I am really also unique"
assert t2.save, "Should now save t2 as unique"
end
diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb
index 11912ca1cc..c02b3241cd 100644
--- a/activerecord/test/cases/validations_repair_helper.rb
+++ b/activerecord/test/cases/validations_repair_helper.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def repair_validations(*model_classes)
teardown do
model_classes.each do |k|
- k.reset_callbacks(:validate)
+ k.clear_validators!
end
end
end
@@ -16,7 +16,7 @@ module ActiveRecord
yield
ensure
model_classes.each do |k|
- k.reset_callbacks(:validate)
+ k.clear_validators!
end
end
end
diff --git a/activerecord/test/fixtures/dog_lovers.yml b/activerecord/test/fixtures/dog_lovers.yml
index d3e5e4a1aa..3f4c6c9e4c 100644
--- a/activerecord/test/fixtures/dog_lovers.yml
+++ b/activerecord/test/fixtures/dog_lovers.yml
@@ -2,3 +2,6 @@ david:
id: 1
bred_dogs_count: 0
trained_dogs_count: 1
+joanna:
+ id: 2
+ dogs_count: 1
diff --git a/activerecord/test/fixtures/dogs.yml b/activerecord/test/fixtures/dogs.yml
index 16d19be2c5..b5eb2c7b74 100644
--- a/activerecord/test/fixtures/dogs.yml
+++ b/activerecord/test/fixtures/dogs.yml
@@ -1,3 +1,4 @@
sophie:
id: 1
trainer_id: 1
+ dog_lover_id: 2
diff --git a/activerecord/test/models/dog.rb b/activerecord/test/models/dog.rb
index 72b7d33a86..b02b8447b8 100644
--- a/activerecord/test/models/dog.rb
+++ b/activerecord/test/models/dog.rb
@@ -1,4 +1,5 @@
class Dog < ActiveRecord::Base
- belongs_to :breeder, :class_name => "DogLover", :counter_cache => :bred_dogs_count
- belongs_to :trainer, :class_name => "DogLover", :counter_cache => :trained_dogs_count
+ belongs_to :breeder, class_name: "DogLover", counter_cache: :bred_dogs_count
+ belongs_to :trainer, class_name: "DogLover", counter_cache: :trained_dogs_count
+ belongs_to :doglover, foreign_key: :dog_lover_id, class_name: "DogLover", counter_cache: true
end
diff --git a/activerecord/test/models/dog_lover.rb b/activerecord/test/models/dog_lover.rb
index a33dc575c5..2c5be94aea 100644
--- a/activerecord/test/models/dog_lover.rb
+++ b/activerecord/test/models/dog_lover.rb
@@ -1,4 +1,5 @@
class DogLover < ActiveRecord::Base
- has_many :trained_dogs, :class_name => "Dog", :foreign_key => :trainer_id
- has_many :bred_dogs, :class_name => "Dog", :foreign_key => :breeder_id
+ has_many :trained_dogs, class_name: "Dog", foreign_key: :trainer_id, dependent: :destroy
+ has_many :bred_dogs, class_name: "Dog", foreign_key: :breeder_id
+ has_many :dogs
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index a952738e84..8fd4898ad6 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -230,14 +230,16 @@ ActiveRecord::Schema.define do
t.integer :access_level, :default => 1
end
- create_table :dog_lovers, :force => true do |t|
- t.integer :trained_dogs_count, :default => 0
- t.integer :bred_dogs_count, :default => 0
+ create_table :dog_lovers, force: true do |t|
+ t.integer :trained_dogs_count, default: 0
+ t.integer :bred_dogs_count, default: 0
+ t.integer :dogs_count, default: 0
end
create_table :dogs, :force => true do |t|
t.integer :trainer_id
t.integer :breeder_id
+ t.integer :dog_lover_id
end
create_table :edges, :force => true, :id => false do |t|
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 9e6b77e2f3..b629688591 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* `ActiveSupport::TimeWithZone` raises `NoMethodError` in proper context.
+ Fixes #9772.
+
+ *Yves Senn*
+
* Fix deletion of empty directories in `ActiveSupport::Cache::FileStore`.
*Charles Jones*
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index a28310032a..b46a331f6a 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.6', '>= 0.6.4')
s.add_dependency 'multi_json', '~> 1.3'
- s.add_dependency 'tzinfo', '~> 0.3.33'
+ s.add_dependency 'tzinfo', '~> 0.3.37'
s.add_dependency 'minitest', '~> 4.2'
s.add_dependency 'thread_safe','~> 0.1'
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index edbe697962..6bfac15289 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -344,7 +344,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def write(name, value, options = nil)
options = merged_options(options)
- instrument(:write, name, options) do |payload|
+ instrument(:write, name, options) do
entry = Entry.new(value, options)
write_entry(namespaced_key(name, options), entry, options)
end
@@ -355,7 +355,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def delete(name, options = nil)
options = merged_options(options)
- instrument(:delete, name) do |payload|
+ instrument(:delete, name) do
delete_entry(namespaced_key(name, options), options)
end
end
@@ -365,7 +365,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def exist?(name, options = nil)
options = merged_options(options)
- instrument(:exist?, name) do |payload|
+ instrument(:exist?, name) do
entry = read_entry(namespaced_key(name, options), options)
entry && !entry.expired?
end
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index b48bdf08e8..998a59c618 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,4 +1,4 @@
-Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
+Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path|
next if File.basename(path, '.rb') == 'logger'
- require "active_support/core_ext/#{File.basename(path, '.rb')}"
+ require path
end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index a87383fe99..e0cd92ae3c 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -19,10 +19,10 @@ module ActiveSupport
# end
#
# By default it uses Marshal to serialize the message. If you want to use
- # another serialization method, you can set the serializer attribute to
- # something that responds to dump and load, e.g.:
+ # another serialization method, you can set the serializer in the options
+ # hash upon initialization:
#
- # @verifier.serializer = YAML
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
class MessageVerifier
class InvalidSignature < StandardError; end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 98c866ac43..4a032b0ad0 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -366,6 +366,8 @@ module ActiveSupport
# TimeWithZone with the existing +time_zone+.
def method_missing(sym, *args, &block)
wrap_with_time_zone time.__send__(sym, *args, &block)
+ rescue NoMethodError => e
+ raise e, e.message.sub(time.inspect, self.inspect), e.backtrace
end
private
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 4b880cb5dc..21a0620c22 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -151,7 +151,7 @@ module ActiveSupport
"Taipei" => "Asia/Taipei",
"Perth" => "Australia/Perth",
"Irkutsk" => "Asia/Irkutsk",
- "Ulaan Bataar" => "Asia/Ulaanbaatar",
+ "Ulaanbaatar" => "Asia/Ulaanbaatar",
"Seoul" => "Asia/Seoul",
"Osaka" => "Asia/Tokyo",
"Sapporo" => "Asia/Tokyo",
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index ec0967fdd7..ca23057189 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,10 +1,11 @@
module ActiveSupport
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActiveSupport as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveSupport.version.segments
+ STRING = ActiveSupport.version.to_s
end
end
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 4551dd2f2d..27c64c4dca 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -37,6 +37,12 @@ module ActiveSupport
{}
else
@dbf = DocumentBuilderFactory.new_instance
+ # secure processing of java xml
+ # http://www.ibm.com/developerworks/xml/library/x-tipcfsx/index.html
+ @dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
+ @dbf.setFeature("http://xml.org/sax/features/external-general-entities", false)
+ @dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false)
+ @dbf.setFeature(javax.xml.XMLConstants::FEATURE_SECURE_PROCESSING, true)
xml_string_reader = StringReader.new(data)
xml_input_source = InputSource.new(xml_string_reader)
doc = @dbf.new_document_builder.parse(xml_input_source)
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 0f5699fd63..98a87ab9e6 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -779,6 +779,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", (twz - 1.year).inspect
end
+ def test_no_method_error_has_proper_context
+ e = assert_raises(NoMethodError) {
+ @twz.this_method_does_not_exist
+ }
+ assert_equal "undefined method `this_method_does_not_exist' for Fri, 31 Dec 1999 19:00:00 EST -05:00:Time", e.message
+ assert_no_match "rescue", e.backtrace.first
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
diff --git a/activesupport/test/fixtures/xml/jdom_doctype.dtd b/activesupport/test/fixtures/xml/jdom_doctype.dtd
new file mode 100644
index 0000000000..89480496ef
--- /dev/null
+++ b/activesupport/test/fixtures/xml/jdom_doctype.dtd
@@ -0,0 +1 @@
+<!ENTITY a "external entity">
diff --git a/activesupport/test/fixtures/xml/jdom_entities.txt b/activesupport/test/fixtures/xml/jdom_entities.txt
new file mode 100644
index 0000000000..0337fdaa08
--- /dev/null
+++ b/activesupport/test/fixtures/xml/jdom_entities.txt
@@ -0,0 +1 @@
+<!ENTITY a "hello">
diff --git a/activesupport/test/fixtures/xml/jdom_include.txt b/activesupport/test/fixtures/xml/jdom_include.txt
new file mode 100644
index 0000000000..239ca3afaf
--- /dev/null
+++ b/activesupport/test/fixtures/xml/jdom_include.txt
@@ -0,0 +1 @@
+include me
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 4806ce07f6..22cb61ffd6 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -324,7 +324,7 @@ class InflectorTest < ActiveSupport::TestCase
end
def test_underscore_as_reverse_of_dasherize
- UnderscoresToDashes.each do |underscored, dasherized|
+ UnderscoresToDashes.each_key do |underscored|
assert_equal(underscored, ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.dasherize(underscored)))
end
end
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index b5d8142458..ce91c443e1 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -17,7 +17,7 @@ class TransliterateTest < ActiveSupport::TestCase
# some reason or other are floating in the middle of all the letters.
string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include?(c)}.pack("U*")
string.each_char do |char|
- assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(string)
+ assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(char)
end
end
diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index f77d78d42c..2dfdde5c62 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -3,9 +3,12 @@ if RUBY_PLATFORM =~ /java/
require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
+
class JDOMEngineTest < ActiveSupport::TestCase
include ActiveSupport
+ FILES_DIR = File.dirname(__FILE__) + '/../fixtures/xml'
+
def setup
@default_backend = XmlMini.backend
XmlMini.backend = 'JDOM'
@@ -30,10 +33,41 @@ if RUBY_PLATFORM =~ /java/
assert_equal 'image/png', file.content_type
end
+ def test_not_allowed_to_expand_entities_to_files
+ attack_xml = <<-EOT
+ <!DOCTYPE member [
+ <!ENTITY a SYSTEM "file://#{FILES_DIR}/jdom_include.txt">
+ ]>
+ <member>x&a;</member>
+ EOT
+ assert_equal 'x', Hash.from_xml(attack_xml)["member"]
+ end
+
+ def test_not_allowed_to_expand_parameter_entities_to_files
+ attack_xml = <<-EOT
+ <!DOCTYPE member [
+ <!ENTITY % b SYSTEM "file://#{FILES_DIR}/jdom_entities.txt">
+ %b;
+ ]>
+ <member>x&a;</member>
+ EOT
+ assert_raise Java::OrgXmlSax::SAXParseException do
+ assert_equal 'x', Hash.from_xml(attack_xml)["member"]
+ end
+ end
+
+
+ def test_not_allowed_to_load_external_doctypes
+ attack_xml = <<-EOT
+ <!DOCTYPE member SYSTEM "file://#{FILES_DIR}/jdom_doctype.dtd">
+ <member>x&a;</member>
+ EOT
+ assert_equal 'x', Hash.from_xml(attack_xml)["member"]
+ end
+
def test_exception_thrown_on_expansion_attack
- assert_raise NativeException do
+ assert_raise Java::OrgXmlSax::SAXParseException do
attack_xml = <<-EOT
- <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE member [
<!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
<!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
diff --git a/ci/travis.rb b/ci/travis.rb
index b03ac4fe35..9029c3f41c 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -109,7 +109,7 @@ end
# puts " #{`uname -a`}"
# puts " #{`ruby -v`}"
# puts " #{`mysql --version`}"
-# # puts " #{`pg_config --version`}"
+# puts " #{`pg_config --version`}"
# puts " SQLite3: #{`sqlite3 -version`}"
# `gem env`.each_line {|line| print " #{line}"}
# puts " Bundled gems:"
@@ -117,7 +117,7 @@ end
# puts " Local gems:"
# `gem list`.each_line {|line| print " #{line}"}
-failures = results.select { |key, value| value == false }
+failures = results.select { |key, value| !value }
if failures.empty?
puts
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index b355c7d91a..dca00b43cd 100644
--- a/guides/code/getting_started/Gemfile
+++ b/guides/code/getting_started/Gemfile
@@ -23,7 +23,7 @@ gem 'jquery-rails'
gem 'turbolinks'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-gem 'jbuilder', '~> 1.0.1'
+gem 'jbuilder', '~> 1.2'
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'
diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html
index ae7b8649ae..3d287b135d 100644
--- a/guides/code/getting_started/public/404.html
+++ b/guides/code/getting_started/public/404.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
- width: 33em;
+
+ body > p {
+ width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow: 0 3px 8px rgba(50,50,50,0.17);
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html
index 0b64eb4ae9..3b946bf4a4 100644
--- a/guides/code/getting_started/public/422.html
+++ b/guides/code/getting_started/public/422.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>The change you wanted was rejected (422)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
+
+ body > p {
width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow: 0 3px 8px rgba(50,50,50,0.17);
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html
index 9641851e74..ccc4ad5656 100644
--- a/guides/code/getting_started/public/500.html
+++ b/guides/code/getting_started/public/500.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
- width: 33em;
+
+ body > p {
+ width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow: 0 3px 8px rgba(50,50,50,0.17);
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index cef82f3784..802455f612 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -31,20 +31,20 @@ Documentation
The internal documentation of Rails, in the form of code comments, has been improved in numerous places. In addition, the [Ruby on Rails Guides](http://guides.rubyonrails.org/) project is the definitive source for information on major Rails components. In its first official release, the Guides page includes:
-* [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html)
-* [Rails Database Migrations](http://guides.rubyonrails.org/migrations.html)
-* [Active Record Associations](http://guides.rubyonrails.org/association_basics.html)
-* [Active Record Query Interface](http://guides.rubyonrails.org/active_record_querying.html)
-* [Layouts and Rendering in Rails](http://guides.rubyonrails.org/layouts_and_rendering.html)
-* [Action View Form Helpers](http://guides.rubyonrails.org/form_helpers.html)
-* [Rails Routing from the Outside In](http://guides.rubyonrails.org/routing.html)
-* [Action Controller Overview](http://guides.rubyonrails.org/action_controller_overview.html)
-* [Rails Caching](http://guides.rubyonrails.org/caching_with_rails.html)
-* [A Guide to Testing Rails Applications](http://guides.rubyonrails.org/testing.html)
-* [Securing Rails Applications](http://guides.rubyonrails.org/security.html)
-* [Debugging Rails Applications](http://guides.rubyonrails.org/debugging_rails_applications.html)
-* [Performance Testing Rails Applications](http://guides.rubyonrails.org/performance_testing.html)
-* [The Basics of Creating Rails Plugins](http://guides.rubyonrails.org/plugins.html)
+* [Getting Started with Rails](getting_started.html)
+* [Rails Database Migrations](migrations.html)
+* [Active Record Associations](association_basics.html)
+* [Active Record Query Interface](active_record_querying.html)
+* [Layouts and Rendering in Rails](layouts_and_rendering.html)
+* [Action View Form Helpers](form_helpers.html)
+* [Rails Routing from the Outside In](routing.html)
+* [Action Controller Overview](action_controller_overview.html)
+* [Rails Caching](caching_with_rails.html)
+* [A Guide to Testing Rails Applications](testing.html)
+* [Securing Rails Applications](security.html)
+* [Debugging Rails Applications](debugging_rails_applications.html)
+* [Performance Testing Rails Applications](performance_testing.html)
+* [The Basics of Creating Rails Plugins](plugins.html)
All told, the Guides provide tens of thousands of words of guidance for beginning and intermediate Rails developers.
@@ -236,7 +236,7 @@ This will enable recognition of (among others) these routes:
* Lead Contributor: [S. Brent Faulkner](http://www.unwwwired.net/)
* More information:
- * [Rails Routing from the Outside In](http://guides.rubyonrails.org/routing.html#nested-resources)
+ * [Rails Routing from the Outside In](routing.html#nested-resources)
* [What's New in Edge Rails: Shallow Routes](http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes)
### Method Arrays for Member or Collection Routes
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 7e0a8a43d4..e01d0d57ea 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -194,7 +194,7 @@ class PeopleController < ActionController::Base
# This will pass with flying colors as long as there's a person key
# in the parameters, otherwise it'll raise a
- # ActionController::MissingParameter exception, which will get
+ # ActionController::ParameterMissing exception, which will get
# caught by ActionController::Base and turned into that 400 Bad
# Request reply.
def update
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 31182e9aed..a0d962f9c4 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -564,31 +564,8 @@ config.action_mailer.smtp_settings = {
Mailer Testing
--------------
-By default Action Mailer does not send emails in the test environment. They are just added to the `ActionMailer::Base.deliveries` array.
-
-Testing mailers normally involves two things: One is that the mail was queued, and the other one that the email is correct. With that in mind, we could test our example mailer from above like so:
-
-```ruby
-class UserMailerTest < ActionMailer::TestCase
- def test_welcome_email
- user = users(:some_user_in_your_fixtures)
-
- # Send the email, then test that it got queued
- email = UserMailer.welcome_email(user).deliver
- assert !ActionMailer::Base.deliveries.empty?
-
- # Test the body of the sent email contains what we expect it to
- assert_equal [user.email], email.to
- assert_equal 'Welcome to My Awesome Site', email.subject
- assert_match "<h1>Welcome to example.com, #{user.name}</h1>", email.body.to_s
- assert_match 'you have joined to example.com community', email.body.to_s
- end
-end
-```
-
-In the test we send the email and store the returned object in the `email` variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect.
-
-NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in `ActionMailer::TestCase` tests. If you want to have a clean slate outside Action Mailer tests, you can reset it manually with: `ActionMailer::Base.deliveries.clear`
+You can find detailed instructions on how to test your mailers in our
+[testing guide](testing.html#testing-your-mailers).
Intercepting Emails
-------------------
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 00939c4ff2..ac76f00832 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -676,7 +676,12 @@ There are now no strict dependencies on what the class is, only what the API for
Within an engine, there may come a time where you wish to use things such as initializers, internationalization or other configuration options. The great news is that these things are entirely possible because a Rails engine shares much the same functionality as a Rails application. In fact, a Rails application's functionality is actually a superset of what is provided by engines!
-If you wish to use an initializer — code that should run before the engine is loaded — the place for it is the `config/initializers` folder. This directory's functionality is explained in the [Initializers section](http://guides.rubyonrails.org/configuring.html#initializers) of the Configuring guide, and works precisely the same way as the `config/initializers` directory inside an application. Same goes for if you want to use a standard initializer.
+If you wish to use an initializer — code that should run before the engine is
+loaded — the place for it is the `config/initializers` folder. This directory's
+functionality is explained in the
+[Initializers section](configuring.html#initializers) of the Configuring guide,
+and works precisely the same way as the `config/initializers` directory inside
+an application. Same goes for if you want to use a standard initializer.
For locales, simply place the locale files in the `config/locales` directory, just like you would in an application.
@@ -918,7 +923,7 @@ initializer "blorgh.assets.precompile" do |app|
end
```
-For more information, read the [Asset Pipeline guide](http://guides.rubyonrails.org/asset_pipeline.html)
+For more information, read the [Asset Pipeline guide](asset_pipeline.html)
### Other gem dependencies
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index b8681d493a..817a732051 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -906,7 +906,21 @@ If the associated object is already saved, `fields_for` autogenerates a hidden i
### The Controller
-You do not need to write any specific controller code to use nested attributes. Create and update records as you would with a simple form.
+As usual you need to
+[whitelist the parameters](action_controller_overview.html#strong-parameters) in
+the controller before you pass them to the model:
+
+```ruby
+def create
+ @person = Person.new(person_params)
+ # ...
+end
+
+private
+def person_params
+ params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street])
+end
+```
### Removing Objects
@@ -937,6 +951,16 @@ If the hash of attributes for an object contains the key `_destroy` with a value
<% end %>
```
+Don't forget to update the whitelisted params in your controller to also include
+the `_destroy` field:
+
+```ruby
+def person_params
+ params.require(:person).
+ permit(:name, addresses_attributes: [:id, :kind, :street, :_destroy])
+end
+```
+
### Preventing Empty Records
It is often useful to ignore sets of fields that the user has not filled in. You can control this by passing a `:reject_if` proc to `accepts_nested_attributes_for`. This proc will be called with each hash of attributes submitted by the form. If the proc returns `false` then Active Record will not build an associated object for that hash. The example below only tries to build an address if the `kind` attribute is set.
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 8ba5fa4601..412f2faaaa 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -266,7 +266,7 @@ def start
url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
- puts "=> Call with -d to detach" unless options[:daemonize]
+ puts "=> Run `rails server -h` for more startup options"
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 1937cbf17a..3b1c159aec 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -927,19 +927,24 @@ require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
tests UserMailer
test "invite" do
- @expected.from = 'me@example.com'
- @expected.to = 'friend@example.com'
- @expected.subject = "You have been invited by #{@expected.from}"
- @expected.body = read_fixture('invite')
- @expected.date = Time.now
-
- assert_equal @expected.encoded, UserMailer.create_invite('me@example.com', 'friend@example.com', @expected.date).encoded
+ # Send the email, then test that it got queued
+ email = UserMailer.create_invite('me@example.com',
+ 'friend@example.com', Time.now).deliver
+ assert !ActionMailer::Base.deliveries.empty?
+
+ # Test the body of the sent email contains what we expect it to
+ assert_equal ['me@example.com'], email.from
+ assert_equal ['friend@example.com'], email.to
+ assert_equal 'You have been invited by me@example.com', email.subject
+ assert_equal read_fixture('invite').join, email.body.to_s
end
-
end
```
-In this test, `@expected` is an instance of `TMail::Mail` that you can use in your tests. It is defined in `ActionMailer::TestCase`. The test above uses `@expected` to construct an email, which it then asserts with email created by the custom mailer. The `invite` fixture is the body of the email and is used as the sample content to assert against. The helper `read_fixture` is used to read in the content from this file.
+In the test we send the email and store the returned object in the `email`
+variable. We then ensure that it was sent (the first assert), then, in the
+second batch of assertions, we ensure that the email does indeed contain what we
+expect. The helper `read_fixture` is used to read in the content from this file.
Here's the content of the `invite` fixture:
@@ -951,9 +956,17 @@ You have been invited.
Cheers!
```
-This is the right time to understand a little more about writing tests for your mailers. The line `ActionMailer::Base.delivery_method = :test` in `config/environments/test.rb` sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (`ActionMailer::Base.deliveries`).
+This is the right time to understand a little more about writing tests for your
+mailers. The line `ActionMailer::Base.delivery_method = :test` in
+`config/environments/test.rb` sets the delivery method to test mode so that
+email will not actually be delivered (useful to avoid spamming your users while
+testing) but instead it will be appended to an array
+(`ActionMailer::Base.deliveries`).
-This way, emails are not actually sent, simply constructed. The precise content of the email can then be checked against what is expected, as in the example above.
+NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in
+`ActionMailer::TestCase` tests. If you want to have a clean slate outside Action
+Mailer tests, you can reset it manually with:
+`ActionMailer::Base.deliveries.clear`
### Functional Testing
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 0941bc7e58..8ad2e2bdb4 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -78,7 +78,17 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
### Action Pack
-* Rails 4.0 introduces a new `UpgradeSignatureToEncryptionCookieStore` cookie store. This is useful for upgrading apps using the old default `CookieStore` to the new default `EncryptedCookieStore`. To use this transitional cookie store, you'll want to leave your existing `secret_token` in place, add a new `secret_key_base`, and change your `session_store` like so:
+* Rails 4.0 introduces `ActiveSupport::KeyGenerator` and uses this as a base from which to generate and verify signed cookies (among other things). Existing signed cookies generated with Rails 3.x will be transparently upgraded if you leave your existing `secret_token` in place and add the new `secret_key_base`.
+
+```ruby
+ # config/initializers/secret_token.rb
+ Myapp::Application.config.secret_token = 'existing secret token'
+ Myapp::Application.config.secret_key_base = 'new secret key base'
+```
+
+Please note that you should wait to set `secret_key_base` until you have 100% of your userbase on Rails 4.x and are reasonably sure you will not need to rollback to Rails 3.x. This is because cookies signed based on the new `secret_key_base` in Rails 4.x are not backwards compatible with Rails 3.x. You are free to leave your existing `secret_token` in place, not set the new `secret_key_base`, and ignore the deprecation warnings until you are reasonably sure that your upgrade is otherwise complete.
+
+* Rails 4.0 introduces a new `UpgradeSignatureToEncryptionCookieStore` cookie store. This is useful for upgrading apps using the old default `CookieStore` to the new default `EncryptedCookieStore` which leverages the new `ActiveSupport::KeyGenerator`. To use this transitional cookie store, you'll want to leave your existing `secret_token` in place, add a new `secret_key_base`, and change your `session_store` like so:
```ruby
# config/initializers/session_store.rb
@@ -103,32 +113,23 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
* Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`.
-* Rails 4.0 correctly prefers the first named route defined in `config/routes.rb` if a clashing route is found later. Check the output of `rake routes` before upgrading and remove unused named routes to avoid issues.
+* Rails 4.0 raises an `ArgumentError` if clashing named routes are defined. This can be triggered by explicitly defined named routes or by the `resources` method. Here are two examples that clash with routes named `example_path`:
```ruby
- # config/routes.rb
get 'one' => 'test#example', as: :example
get 'two' => 'test#example', as: :example
-
- # Rails 3
- <%= example_path %> # => '/two'
-
- # Rails 4
- <%= example_path %> # => '/one'
```
```ruby
- # config/routes.rb
resources :examples
get 'clashing/:id' => 'test#example', as: :example
-
- # Rails 3
- <%= example_path(1) %> # => '/clashing/1'
-
- # Rails 4
- <%= example_path(1) %> # => '/examples/1'
```
+In the first case, you can simply avoid using the same name for multiple
+routes. In the second, you can use the `only` or `except` options provided by
+the `resources` method to restrict the routes created as detailed in the
+[Routing Guide](routing.html#restricting-the-routes-created).
+
* Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, for example:
```ruby
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 60a823de15..2727f1a85d 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,25 @@
## Rails 4.0.0 (unreleased) ##
+* Allow vanilla apps to render CoffeeScript templates in production
+
+ Vanilla apps already render CoffeeScript templates in development and test
+ environments. With this change, the production behavior matches that of
+ the other environments.
+
+ Effectively, this meant moving coffee-rails (and the JavaScript runtime on
+ which it is dependent) from the :assets group to the top-level of the
+ generated Gemfile.
+
+ *Gabe Kopley*
+
+* `Rails.version` now returns an instance of `Gem::Version`
+
+ *Charlie Somerville*
+
+* Don't generate a scaffold.css when --no-assets is specified
+
+ *Kevin Glowacz*
+
* Add support for generate scaffold password:digest
* adds password_digest attribute to the migration
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index bb98bbe5bf..84f8b82ad5 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -82,10 +82,6 @@ module Rails
groups
end
- def version
- VERSION::STRING
- end
-
def public_path
application && Pathname.new(application.paths["public"].first)
end
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
index 6c9c53fc69..19c2226619 100644
--- a/railties/lib/rails/all.rb
+++ b/railties/lib/rails/all.rb
@@ -1,5 +1,9 @@
require "rails"
+if defined?(Rake) && Rake.application.top_level_tasks.grep(/^test(?::|$)/).any?
+ ENV['RAILS_ENV'] ||= 'test'
+end
+
%w(
active_record
action_controller
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 2417bdca21..563905e8b3 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -79,7 +79,7 @@ module Rails
@initialized = false
@reloaders = []
@routes_reloader = nil
- @env_config = nil
+ @app_env_config = nil
@ordered_railties = nil
@railties = nil
end
@@ -134,7 +134,7 @@ module Rails
# * "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt
#
def env_config
- @env_config ||= begin
+ @app_env_config ||= begin
if config.secret_key_base.nil?
ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base in config/initializers/secret_token.rb file. " +
"This should be used instead of the old deprecated config.secret_token in order to use the new EncryptedCookieStore. " +
@@ -149,6 +149,7 @@ module Rails
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.redirect_filter" => config.filter_redirect,
"action_dispatch.secret_token" => config.secret_token,
+ "action_dispatch.secret_key_base" => config.secret_key_base,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
"action_dispatch.logger" => Rails.logger,
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index 2ff29418c6..d7b8ff4439 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -1,7 +1,7 @@
require 'rails/version'
if ['--version', '-v'].include?(ARGV.first)
- puts "Rails #{Rails::VERSION::STRING}"
+ puts "Rails #{Rails.version}"
exit(0)
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index ddf45d196a..e3119ecf22 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -42,6 +42,7 @@ module Rails
set_environment
end
+ # TODO: this is no longer required but we keep it for the moment to support older config.ru files.
def app
@app ||= begin
app = super
@@ -61,7 +62,7 @@ module Rails
url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
- puts "=> Call with -d to detach" unless options[:daemonize]
+ puts "=> Run `rails server -h` for more startup options"
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 4e703151ba..4e05c32f74 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -1,7 +1,7 @@
require 'digest/md5'
require 'securerandom'
require 'active_support/core_ext/string/strip'
-require 'rails/version' unless defined?(Rails::VERSION)
+require 'rails/version' unless defined?(Rails.version)
require 'rbconfig'
require 'open-uri'
require 'uri'
@@ -142,7 +142,7 @@ module Rails
else
<<-GEMFILE.strip_heredoc
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
- gem 'rails', '#{Rails::VERSION::STRING}'
+ gem 'rails', '#{Rails.version}'
GEMFILE
end
end
@@ -178,29 +178,25 @@ module Rails
return if options[:skip_sprockets]
gemfile = if options.dev? || options.edge?
- <<-GEMFILE
+ <<-GEMFILE.gsub(/^ {12}/, '')
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sprockets-rails', github: 'rails/sprockets-rails'
gem 'sass-rails', github: 'rails/sass-rails'
- gem 'coffee-rails', github: 'rails/coffee-rails'
-
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes
- #{javascript_runtime_gemfile_entry}
+ #{coffee_gemfile_entry if options[:skip_javascript]}
+ #{javascript_runtime_gemfile_entry(2) if options[:skip_javascript]}
gem 'uglifier', '>= 1.0.3'
end
GEMFILE
else
- <<-GEMFILE
+ <<-GEMFILE.gsub(/^ {12}/, '')
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 4.0.0.beta1'
- gem 'coffee-rails', '~> 4.0.0.beta1'
-
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes
- #{javascript_runtime_gemfile_entry}
+ #{coffee_gemfile_entry if options[:skip_javascript]}
+ #{javascript_runtime_gemfile_entry(2) if options[:skip_javascript]}
gem 'uglifier', '>= 1.0.3'
end
GEMFILE
@@ -209,10 +205,24 @@ module Rails
gemfile.strip_heredoc.gsub(/^[ \t]*$/, '')
end
+ def coffee_gemfile_entry
+ if options.dev? || options.edge?
+ "gem 'coffee-rails', github: 'rails/coffee-rails'"
+ else
+ "gem 'coffee-rails', '~> 4.0.0.beta1'"
+ end
+ end
+
def javascript_gemfile_entry
+ args = {'jquery' => ", github: 'rails/jquery-rails'"}
+
unless options[:skip_javascript]
- <<-GEMFILE.strip_heredoc
- gem '#{options[:javascript]}-rails'
+ <<-GEMFILE.gsub(/^ {12}/, '').strip_heredoc
+ #{javascript_runtime_gemfile_entry}
+ # Use CoffeeScript for .js.coffee assets and views
+ #{coffee_gemfile_entry}
+
+ gem '#{options[:javascript]}-rails'#{args[options[:javascript]]}
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
@@ -220,12 +230,16 @@ module Rails
end
end
- def javascript_runtime_gemfile_entry
- if defined?(JRUBY_VERSION)
- "gem 'therubyrhino'\n"
+ def javascript_runtime_gemfile_entry(n_spaces=0)
+ runtime = if defined?(JRUBY_VERSION)
+ "gem 'therubyrhino'"
else
- "# gem 'therubyracer', platforms: :ruby\n"
+ "# gem 'therubyracer', platforms: :ruby"
end
+ <<-GEMFILE.gsub(/^ {10}/, '')
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ #{" "*n_spaces}#{runtime}
+ GEMFILE
end
def bundle_command(command)
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
index fcfbc6b07a..5bc2a619e8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -1,4 +1,4 @@
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
-run <%= app_const %>
+run Rails.application
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
index e5caab3672..efccf72d3d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -1,6 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Your secret key for verifying the integrity of signed cookies.
+# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html
index 0ee82d3722..a0daa0c156 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/404.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/404.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
- width: 33em;
+
+ body > p {
+ width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow:0 3px 8px rgba(50,50,50,0.17);
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html
index f1f32b83ae..fbb4b84d72 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/422.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/422.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>The change you wanted was rejected (422)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
+
+ body > p {
width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow:0 3px 8px rgba(50,50,50,0.17);
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html
index 9417de0cc0..e9052d35bf 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/500.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/500.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
- width: 33em;
+
+ body > p {
+ width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow:0 3px 8px rgba(50,50,50,0.17);
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
index f7c12e67dd..6373ca711e 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
s.test_files = Dir["test/**/*"]
<% end -%>
- <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>"
+ <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails.version %>"
<% unless options[:skip_active_record] -%>
s.add_development_dependency "<%= gem_for_database %>"
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index 3f2b78f2fd..de00ab057d 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -1,7 +1,7 @@
source "https://rubygems.org"
<% if options[:skip_gemspec] -%>
-<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>"
+<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails.version %>"
<% else -%>
# Declare your gem's dependencies in <%= name %>.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index 36589d65e2..2a0522e81c 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -10,6 +10,7 @@ module Rails
class_option :stylesheet_engine, desc: "Engine for Stylesheets"
def handle_skip
+ @options = @options.merge(stylesheets: false) unless options[:assets]
@options = @options.merge(stylesheet_engine: false) unless options[:stylesheets]
end
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index 592e74726e..f06ce659c5 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -29,7 +29,7 @@ module Rails
def framework_version(framework)
if Object.const_defined?(framework.classify)
require "#{framework}/version"
- "#{framework.classify}::VERSION::STRING".constantize
+ framework.classify.constantize.version.to_s
end
end
@@ -75,7 +75,7 @@ module Rails
# The Rails version.
property 'Rails version' do
- Rails::VERSION::STRING
+ Rails.version.to_s
end
property 'JavaScript Runtime' do
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 87fc7690ac..d1d02e086d 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -1,10 +1,11 @@
module Rails
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded Rails as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = Rails.version.segments
+ STRING = Rails.version.to_s
end
end
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index 1eddfac664..b3b40448c0 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -48,7 +48,7 @@ module ApplicationTests
assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body)
end
- test "assets aren't concatened when compile is true is on and debug_assets params is true" do
+ test "assets aren't concatenated when compile is true is on and debug_assets params is true" do
add_to_env_config "production", "config.assets.compile = true"
ENV["RAILS_ENV"] = "production"
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 638df8ca23..e5503b1eff 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -86,8 +86,8 @@ module ApplicationTests
def test_precompile_does_not_hit_the_database
app_file "app/assets/javascripts/application.js", "alert();"
app_file "app/assets/javascripts/foo/application.js", "alert();"
- app_file "app/controllers/user_controller.rb", <<-eoruby
- class UserController < ApplicationController; end
+ app_file "app/controllers/users_controller.rb", <<-eoruby
+ class UsersController < ApplicationController; end
eoruby
app_file "app/models/user.rb", <<-eoruby
class User < ActiveRecord::Base; end
@@ -221,7 +221,7 @@ module ApplicationTests
assert !defined?(Uglifier)
end
- test "precompile properly refers files referenced with asset_path and and run in the provided RAILS_ENV" do
+ test "precompile properly refers files referenced with asset_path and runs in the provided RAILS_ENV" do
app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
# digest is default in false, we must enable it for test environment
add_to_env_config "test", "config.assets.digest = true"
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 7b45623f6c..1acf03f35a 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -599,7 +599,7 @@ module ApplicationTests
assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters
end
- test "config.action_controller.action_on_unpermitted_parameters is :log by defaul on test" do
+ test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do
ENV["RAILS_ENV"] = "test"
require "#{app_path}/config/environment"
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index af495bb450..80700a1d64 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -126,12 +126,6 @@ class FullStackConsoleTest < ActiveSupport::TestCase
assert_output "> "
end
- def kill(pid)
- Process.kill('QUIT', pid)
- Process.wait(pid)
- rescue Errno::ESRCH
- end
-
def spawn_console
pid = Process.spawn(
"#{app_path}/bin/rails console --sandbox",
@@ -148,15 +142,13 @@ class FullStackConsoleTest < ActiveSupport::TestCase
write_prompt "Post.count", "=> 0"
write_prompt "Post.create"
write_prompt "Post.count", "=> 1"
-
- kill pid
+ @master.puts "quit"
pid = spawn_console
write_prompt "Post.count", "=> 0"
write_prompt "Post.transaction { Post.create; raise }"
write_prompt "Post.count", "=> 0"
- ensure
- kill pid if pid
+ @master.puts "quit"
end
end
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 489b7ddb92..17d0b10b70 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -45,7 +45,7 @@ module ApplicationTests
end
# Load paths
- test "no config locales dir present should return empty load path" do
+ test "no config locales directory present should return empty load path" do
FileUtils.rm_rf "#{app_path}/config/locales"
load_app
assert_equal [], Rails.application.config.i18n.load_path
diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb
index 31811e7f92..9b18c329ec 100644
--- a/railties/test/application/initializers/load_path_test.rb
+++ b/railties/test/application/initializers/load_path_test.rb
@@ -23,7 +23,7 @@ module ApplicationTests
assert $:.include?("#{app_path}/app/models")
end
- test "initializing an application allows to load code on lib path inside application class definitation" do
+ test "initializing an application allows to load code on lib path inside application class definition" do
app_file "lib/foo.rb", <<-RUBY
module Foo; end
RUBY
diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb
index 18af7abafc..bbb7627be9 100644
--- a/railties/test/application/middleware/cookies_test.rb
+++ b/railties/test/application/middleware/cookies_test.rb
@@ -33,7 +33,7 @@ module ApplicationTests
assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie
end
- test 'always_write_cookie can be overrided' do
+ test 'always_write_cookie can be overridden' do
add_to_config <<-RUBY
config.action_dispatch.always_write_cookie = false
RUBY
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 0697035871..b813a7f6bb 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -232,8 +232,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
assert_file "Gemfile" do |content|
assert_no_match(/sass-rails/, content)
- assert_no_match(/coffee-rails/, content)
assert_no_match(/uglifier/, content)
+ assert_match(/coffee-rails/, content)
end
assert_file "config/environments/development.rb" do |content|
assert_no_match(/config\.assets\.debug = true/, content)
@@ -276,7 +276,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_match %r{^//= require jquery}, contents
assert_match %r{^//= require jquery_ujs}, contents
end
- assert_gem "jquery-rails"
+ assert_file "Gemfile", /^gem 'jquery-rails'/
end
def test_other_javascript_libraries
@@ -293,6 +293,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "app/assets/javascripts/application.js" do |contents|
assert_no_match %r{^//=\s+require\s}, contents
end
+ assert_file "Gemfile" do |content|
+ assert_match(/coffee-rails/, content)
+ end
end
def test_inclusion_of_debugger
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 34441ef679..48425cbf81 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -292,7 +292,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
assert_file "Gemfile" do |contents|
assert_no_match('gemspec', contents)
- assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents)
+ assert_match(/gem "rails", "~> #{Rails.version}"/, contents)
assert_match(/group :development do\n gem "sqlite3"\nend/, contents)
assert_no_match(/# gem "jquery-rails"/, contents)
end
@@ -303,7 +303,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
assert_file "Gemfile" do |contents|
assert_no_match('gemspec', contents)
- assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents)
+ assert_match(/gem "rails", "~> #{Rails.version}"/, contents)
assert_match(/group :development do\n gem "sqlite3"\nend/, contents)
end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index b29d1e018e..25f299118c 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -241,7 +241,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
def test_scaffold_generator_no_assets
run_generator [ "posts", "--no-assets" ]
- assert_file "app/assets/stylesheets/scaffold.css"
+ assert_no_file "app/assets/stylesheets/scaffold.css"
assert_no_file "app/assets/javascripts/posts.js"
assert_no_file "app/assets/stylesheets/posts.css"
end
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index b9fb071d23..5b9088cb64 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -38,7 +38,7 @@ class InfoTest < ActiveSupport::TestCase
end
def test_framework_version
- assert_property 'Active Support version', ActiveSupport::VERSION::STRING
+ assert_property 'Active Support version', ActiveSupport.version.to_s
end
def test_frameworks_exist
diff --git a/tasks/release.rb b/tasks/release.rb
index 650b381e0f..cf5b6d6843 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -23,20 +23,8 @@ directory "pkg"
file = Dir[glob].first
ruby = File.read(file)
- major, minor, tiny, pre = version.split('.')
- pre = pre ? pre.inspect : "nil"
-
- ruby.gsub!(/^(\s*)MAJOR = .*?$/, "\\1MAJOR = #{major}")
- raise "Could not insert MAJOR in #{file}" unless $1
-
- ruby.gsub!(/^(\s*)MINOR = .*?$/, "\\1MINOR = #{minor}")
- raise "Could not insert MINOR in #{file}" unless $1
-
- ruby.gsub!(/^(\s*)TINY = .*?$/, "\\1TINY = #{tiny}")
- raise "Could not insert TINY in #{file}" unless $1
-
- ruby.gsub!(/^(\s*)PRE = .*?$/, "\\1PRE = #{pre}")
- raise "Could not insert PRE in #{file}" unless $1
+ ruby.gsub!(/^(\s*)Gem::Version\.new .*?$/, "\\1Gem::Version.new \"#{version}\"")
+ raise "Could not insert Gem::Version in #{file}" unless $1
File.open(file, 'w') { |f| f.write ruby }
end
diff --git a/version.rb b/version.rb
index 87fc7690ac..367d0f6546 100644
--- a/version.rb
+++ b/version.rb
@@ -1,10 +1,10 @@
module Rails
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = Rails.version.segments
+ STRING = Rails.version.to_s
end
end