aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/lib/action_mailer/base.rb9
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb4
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb18
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb51
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb28
-rw-r--r--activerecord/CHANGELOG.md16
-rw-r--r--activerecord/lib/active_record/associations.rb8
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb1
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb6
-rw-r--r--activerecord/lib/active_record/autosave_association.rb8
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb81
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/binary.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/boolean.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/date.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/date_time.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/decimal.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/float.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/integer.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/string.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/text.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/time.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/value.rb17
-rw-r--r--activerecord/lib/active_record/counter_cache.rb7
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb10
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb4
-rw-r--r--activerecord/lib/active_record/properties.rb95
-rw-r--r--activerecord/lib/active_record/reflection.rb52
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb27
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb1
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_test.rb16
-rw-r--r--activerecord/test/cases/base_test.rb4
-rw-r--r--activerecord/test/cases/custom_properties_test.rb64
-rw-r--r--activerecord/test/cases/quoting_test.rb28
-rw-r--r--activerecord/test/cases/reflection_test.rb7
-rw-r--r--activerecord/test/cases/relations_test.rb14
-rw-r--r--activerecord/test/schema/schema.rb6
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb1
-rw-r--r--activesupport/lib/active_support/railtie.rb1
-rw-r--r--activesupport/lib/active_support/time.rb2
-rw-r--r--guides/CHANGELOG.md7
-rw-r--r--guides/code/getting_started/app/assets/javascripts/articles.js.coffee (renamed from guides/code/getting_started/app/assets/javascripts/posts.js.coffee)0
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/articles.css.scss (renamed from guides/code/getting_started/app/assets/stylesheets/posts.css.scss)2
-rw-r--r--guides/code/getting_started/app/controllers/articles_controller.rb53
-rw-r--r--guides/code/getting_started/app/controllers/comments_controller.rb12
-rw-r--r--guides/code/getting_started/app/controllers/posts_controller.rb53
-rw-r--r--guides/code/getting_started/app/helpers/articles_helper.rb2
-rw-r--r--guides/code/getting_started/app/helpers/posts_helper.rb2
-rw-r--r--guides/code/getting_started/app/models/article.rb (renamed from guides/code/getting_started/app/models/post.rb)4
-rw-r--r--guides/code/getting_started/app/models/comment.rb2
-rw-r--r--guides/code/getting_started/app/views/articles/_form.html.erb (renamed from guides/code/getting_started/app/views/posts/_form.html.erb)10
-rw-r--r--guides/code/getting_started/app/views/articles/edit.html.erb (renamed from guides/code/getting_started/app/views/posts/edit.html.erb)6
-rw-r--r--guides/code/getting_started/app/views/articles/index.html.erb21
-rw-r--r--guides/code/getting_started/app/views/articles/new.html.erb (renamed from guides/code/getting_started/app/views/posts/new.html.erb)6
-rw-r--r--guides/code/getting_started/app/views/articles/show.html.erb18
-rw-r--r--guides/code/getting_started/app/views/comments/_comment.html.erb4
-rw-r--r--guides/code/getting_started/app/views/comments/_form.html.erb2
-rw-r--r--guides/code/getting_started/app/views/posts/index.html.erb21
-rw-r--r--guides/code/getting_started/app/views/posts/show.html.erb18
-rw-r--r--guides/code/getting_started/app/views/welcome/index.html.erb4
-rw-r--r--guides/code/getting_started/config/routes.rb2
-rw-r--r--guides/code/getting_started/db/migrate/20130122042648_create_articles.rb (renamed from guides/code/getting_started/db/migrate/20130122042648_create_posts.rb)4
-rw-r--r--guides/code/getting_started/db/migrate/20130122045842_create_comments.rb2
-rw-r--r--guides/code/getting_started/db/schema.rb18
-rw-r--r--guides/code/getting_started/test/controllers/articles_controller_test.rb (renamed from guides/code/getting_started/test/controllers/posts_controller_test.rb)2
-rw-r--r--guides/code/getting_started/test/fixtures/articles.yml (renamed from guides/code/getting_started/test/fixtures/posts.yml)0
-rw-r--r--guides/code/getting_started/test/fixtures/comments.yml4
-rw-r--r--guides/code/getting_started/test/helpers/articles_helper_test.rb4
-rw-r--r--guides/code/getting_started/test/helpers/posts_helper_test.rb4
-rw-r--r--guides/code/getting_started/test/models/article_test.rb (renamed from guides/code/getting_started/test/models/post_test.rb)2
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md30
-rw-r--r--railties/CHANGELOG.md4
-rw-r--r--railties/lib/rails/generators.rb28
-rw-r--r--railties/lib/rails/generators/actions.rb17
-rw-r--r--railties/lib/rails/ruby_version_check.rb2
-rw-r--r--railties/test/generators/actions_test.rb22
-rw-r--r--railties/test/generators_test.rb6
93 files changed, 708 insertions, 392 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 652ce0b3d8..5852aeaec2 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -301,12 +301,13 @@ module ActionMailer
# end
# end
#
- # Callbacks in ActionMailer are implemented using AbstractController::Callbacks, so you
- # can define and configure callbacks in the same manner that you would use callbacks in
- # classes that inherit from ActionController::Base.
+ # Callbacks in Action Mailer are implemented using
+ # <tt>AbstractController::Callbacks</tt>, so you can define and configure
+ # callbacks in the same manner that you would use callbacks in classes that
+ # inherit from <tt>ActionController::Base</tt>.
#
# Note that unless you have a specific reason to do so, you should prefer using before_action
- # rather than after_action in your ActionMailer classes so that headers are parsed properly.
+ # rather than after_action in your Action Mailer classes so that headers are parsed properly.
#
# = Previewing emails
#
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index 6452bf616c..06da0dd27e 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -1,4 +1,6 @@
module ActionMailer
+ # Provides helper methods for testing Action Mailer, including #assert_emails
+ # and #assert_no_emails
module TestHelper
# Asserts that the number of emails sent matches the given number.
#
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index a98aec913f..06f80a8fdc 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,7 +1,8 @@
require_relative 'gem_version'
module ActionMailer
- # Returns the version of the currently loaded ActionMailer as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded Action Mailer as a
+ # <tt>Gem::Version</tt>.
def self.version
gem_version
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index cce0d75af4..6ffa242da4 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -148,8 +148,8 @@
// On key press perform a search for matching paths
searchElem.onkeyup = function(e){
var userInput = searchElem.value,
- defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + sanitizePath(userInput) +'):</th></tr>',
- defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + userInput +'):</th></tr>',
+ defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + escape(sanitizePath(userInput)) +'):</th></tr>',
+ defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + escape(userInput) +'):</th></tr>',
noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 92544230b2..f7a06cfed4 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -49,6 +49,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal 'foo: "bar"', response.body
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_getting_nil_session_value
@@ -57,6 +59,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal 'foo: nil', response.body
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_getting_session_value_after_session_reset
@@ -76,6 +80,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_getting_from_nonexistent_session
@@ -85,6 +91,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_equal 'foo: nil', response.body
assert_nil cookies['_session_id'], "should only create session on write, not read"
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_setting_session_value_after_session_reset
@@ -106,6 +114,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_not_equal session_id, response.body
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_getting_session_id
@@ -119,6 +129,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_deserializes_unloaded_class
@@ -133,6 +145,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
end
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_doesnt_write_session_cookie_if_session_id_is_already_exists
@@ -145,6 +159,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_prevents_session_fixation
@@ -160,6 +176,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_not_equal session_id, cookies['_session_id']
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
rescue LoadError, RuntimeError, Dalli::DalliError
$stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again."
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 824cdaa45e..f53cce32b0 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -11,7 +11,7 @@ module ActionView
# the assets exist before linking to them:
#
# image_tag("rails.png")
- # # => <img alt="Rails" src="/assets/rails.png" />
+ # # => <img alt="Rails" src="/images/rails.png" />
# stylesheet_link_tag("application")
# # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
module AssetTagHelper
@@ -20,7 +20,8 @@ module ActionView
include AssetUrlHelper
include TagHelper
- # Returns an HTML script tag for each of the +sources+ provided.
+ # Returns an HTML script tag for each of the +sources+ provided. If
+ # you don't specify an extension, <tt>.js</tt> will be appended automatically.
#
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative
# to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
@@ -33,19 +34,19 @@ module ActionView
# last argument.
#
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
- # source, and include other JavaScript or CoffeeScript files inside the manifest.
+ # source and include other JavaScript or CoffeeScript files inside the manifest.
#
# javascript_include_tag "xmlhr"
- # # => <script src="/assets/xmlhr.js?1284139606"></script>
+ # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
#
# javascript_include_tag "template.jst", extname: false
- # # => <script src="/assets/template.jst?1284139606"></script>
+ # # => <script src="/javascripts/template.jst?1284139606"></script>
#
# javascript_include_tag "xmlhr.js"
- # # => <script src="/assets/xmlhr.js?1284139606"></script>
+ # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
#
# javascript_include_tag "common.javascript", "/elsewhere/cools"
- # # => <script src="/assets/common.javascript?1284139606"></script>
+ # # => <script src="/javascripts/common.javascript?1284139606"></script>
# # <script src="/elsewhere/cools.js?1423139606"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr"
@@ -72,22 +73,22 @@ module ActionView
# apply to all media types.
#
# stylesheet_link_tag "style"
- # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
+ # # => <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style.css"
- # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
+ # # => <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "http://www.example.com/style.css"
# # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style", media: "all"
- # # => <link href="/assets/style.css" media="all" rel="stylesheet" />
+ # # => <link href="/stylesheets/style.css" media="all" rel="stylesheet" />
#
# stylesheet_link_tag "style", media: "print"
- # # => <link href="/assets/style.css" media="print" rel="stylesheet" />
+ # # => <link href="/stylesheets/style.css" media="print" rel="stylesheet" />
#
# stylesheet_link_tag "random.styles", "/css/stylish"
- # # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
+ # # => <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
@@ -158,17 +159,17 @@ module ActionView
# respectively:
#
# favicon_link_tag
- # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
+ # # => <link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
#
# favicon_link_tag 'myicon.ico'
- # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
+ # # => <link href="/images/myicon.ico" rel="shortcut icon" type="image/x-icon" />
#
# Mobile Safari looks for a different link tag, pointing to an image that
# will be used if you add the page to the home screen of an iOS device.
# The following call would generate such a tag:
#
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
- # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
+ # # => <link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
@@ -258,19 +259,19 @@ module ActionView
# ==== Examples
#
# video_tag("trailer")
- # # => <video src="/videos/trailer" />
+ # # => <video src="/videos/trailer"></video>
# video_tag("trailer.ogg")
- # # => <video src="/videos/trailer.ogg" />
+ # # => <video src="/videos/trailer.ogg"></video>
# video_tag("trailer.ogg", controls: true, autobuffer: true)
- # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
+ # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" ></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
- # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png"></video>
# video_tag("/trailers/hd.avi", size: "16x16")
- # # => <video src="/trailers/hd.avi" width="16" height="16" />
+ # # => <video src="/trailers/hd.avi" width="16" height="16"></video>
# video_tag("/trailers/hd.avi", size: "16")
- # # => <video height="16" src="/trailers/hd.avi" width="16" />
+ # # => <video height="16" src="/trailers/hd.avi" width="16"></video>
# video_tag("/trailers/hd.avi", height: '32', width: '32')
- # # => <video height="32" src="/trailers/hd.avi" width="32" />
+ # # => <video height="32" src="/trailers/hd.avi" width="32"></video>
# video_tag("trailer.ogg", "trailer.flv")
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
# video_tag(["trailer.ogg", "trailer.flv"])
@@ -289,11 +290,11 @@ module ActionView
# your public audios directory.
#
# audio_tag("sound")
- # # => <audio src="/audios/sound" />
+ # # => <audio src="/audios/sound"></audio>
# audio_tag("sound.wav")
- # # => <audio src="/audios/sound.wav" />
+ # # => <audio src="/audios/sound.wav"></audio>
# audio_tag("sound.wav", autoplay: true, controls: true)
- # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
+ # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
# audio_tag("sound.wav", "sound.mid")
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
def audio_tag(*sources)
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 2b009ba961..d86e7e490c 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -7,10 +7,10 @@ module ActionView
# urls.
#
# image_path("rails.png")
- # # => "/assets/rails.png"
+ # # => "/images/rails.png"
#
# image_url("rails.png")
- # # => "http://www.example.com/assets/rails.png"
+ # # => "http://www.example.com/images/rails.png"
#
# === Using asset hosts
#
@@ -113,9 +113,9 @@ module ActionView
#
# All other asset *_path helpers delegate through this method.
#
- # asset_path "application.js" # => /application.js
- # asset_path "application", type: :javascript # => /javascripts/application.js
- # asset_path "application", type: :stylesheet # => /stylesheets/application.css
+ # asset_path "application.js" # => /assets/application.js
+ # asset_path "application", type: :javascript # => /assets/application.js
+ # asset_path "application", type: :stylesheet # => /assets/application.css
# asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def asset_path(source, options = {})
return "" unless source.present?
@@ -153,7 +153,7 @@ module ActionView
# All other options provided are forwarded to +asset_path+ call.
#
# asset_url "application.js" # => http://example.com/application.js
- # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/javascripts/application.js
+ # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js
#
def asset_url(source, options = {})
path_to_asset(source, options.merge(:protocol => :request))
@@ -231,7 +231,7 @@ module ActionView
# Computes the path to a javascript asset in the public javascripts directory.
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
# Full paths from the document root will be passed through.
- # Used internally by javascript_include_tag to build the script path.
+ # Used internally by +javascript_include_tag+ to build the script path.
#
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
@@ -251,7 +251,7 @@ module ActionView
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
# Computes the path to a stylesheet asset in the public stylesheets directory.
- # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
+ # If the +source+ filename has no extension, .css will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
@@ -276,9 +276,9 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +image_tag+ to build the image path:
#
- # image_path("edit") # => "/assets/edit"
- # image_path("edit.png") # => "/assets/edit.png"
- # image_path("icons/edit.png") # => "/assets/icons/edit.png"
+ # image_path("edit") # => "/images/edit"
+ # image_path("edit.png") # => "/images/edit.png"
+ # image_path("icons/edit.png") # => "/images/icons/edit.png"
# image_path("/icons/edit.png") # => "/icons/edit.png"
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
#
@@ -342,9 +342,9 @@ module ActionView
# Computes the path to a font asset.
# Full paths from the document root will be passed through.
#
- # font_path("font") # => /assets/font
- # font_path("font.ttf") # => /assets/font.ttf
- # font_path("dir/font.ttf") # => /assets/dir/font.ttf
+ # font_path("font") # => /fonts/font
+ # font_path("font.ttf") # => /fonts/font.ttf
+ # font_path("dir/font.ttf") # => /fonts/dir/font.ttf
# font_path("/dir/font.ttf") # => /dir/font.ttf
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
def font_path(source, options = {})
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 041872034f..d8157d02ab 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,17 @@
+* Add a properties API to allow custom types and type casting behavior
+ to be specified. Will enable many edge cases to be deprecated, and
+ allow for additional interesting features in the future.
+
+ *Sean Griffin*
+
+* Fix has_and_belongs_to_many public reflection.
+ When defining a has_and_belongs_to_many, internally we convert that to two has_many.
+ But as `reflections` is a public API, people expect to see the right macro.
+
+ Fixes #14682.
+
+ *arthurnn*
+
* Fixed serialization for records with an attribute named `format`.
Fixes #15188.
@@ -22,7 +36,7 @@
* Change belongs_to touch to be consistent with timestamp updates
- If a model is set up with a belongs_to: touch relatinoship the parent
+ If a model is set up with a belongs_to: touch relationship the parent
record will only be touched if the record was modified. This makes it
consistent with timestamp updating on the record itself.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 8d77fad2d5..07dfc448e7 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -50,7 +50,7 @@ module ActiveRecord
def initialize(reflection)
through_reflection = reflection.through_reflection
source_reflection_names = reflection.source_reflection_names
- source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
+ source_associations = reflection.through_reflection.klass._reflections.keys
super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
end
end
@@ -151,7 +151,7 @@ module ActiveRecord
association = association_instance_get(name)
if association.nil?
- raise AssociationNotFoundError.new(self, name) unless reflection = self.class.reflect_on_association(name)
+ raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
association = reflection.association_class.new(self, reflection)
association_instance_set(name, association)
end
@@ -1577,6 +1577,8 @@ module ActiveRecord
scope = nil
end
+ habtm_reflection = ActiveRecord::Reflection::AssociationReflection.new(:has_and_belongs_to_many, name, scope, options, self)
+
builder = Builder::HasAndBelongsToMany.new name, self, options
join_model = builder.through_model
@@ -1590,6 +1592,7 @@ module ActiveRecord
Builder::HasMany.define_callbacks self, middle_reflection
Reflection.add_reflection self, middle_reflection.name, middle_reflection
+ middle_reflection.parent_reflection = [name.to_s, habtm_reflection]
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -1610,6 +1613,7 @@ module ActiveRecord
end
has_many name, scope, hm_options, &extension
+ self._reflections[name.to_s].parent_reflection = [name.to_s, habtm_reflection]
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 9ad2d2fb12..4a04303fb8 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -160,7 +160,7 @@ module ActiveRecord
def marshal_load(data)
reflection_name, ivars = data
ivars.each { |name, val| instance_variable_set(name, val) }
- @reflection = @owner.class.reflect_on_association(reflection_name)
+ @reflection = @owner.class._reflect_on_association(reflection_name)
end
def initialize_attributes(record) #:nodoc:
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 30b11c01eb..0ad5206980 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -66,13 +66,13 @@ module ActiveRecord::Associations::Builder
def self.add_left_association(name, options)
belongs_to name, options
- self.left_reflection = reflect_on_association(name)
+ self.left_reflection = _reflect_on_association(name)
end
def self.add_right_association(name, options)
rhs_name = name.to_s.singularize.to_sym
belongs_to rhs_name, options
- self.right_reflection = reflect_on_association(rhs_name)
+ self.right_reflection = _reflect_on_association(rhs_name)
end
}
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index f5e911c739..2727e23870 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -100,7 +100,8 @@ module ActiveRecord
# Hence this method.
def inverse_updates_counter_cache?(reflection = reflection())
counter_name = cached_counter_attribute_name(reflection)
- reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
+ reflection.klass._reflections.values.any? { |inverse_reflection|
+ :belongs_to == inverse_reflection.macro &&
inverse_reflection.counter_cache_column == counter_name
}
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 5842be3a7b..01173b68f3 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -207,7 +207,7 @@ module ActiveRecord
end
def find_reflection(klass, name)
- klass.reflect_on_association(name) or
+ klass._reflect_on_association(name) or
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 42571d6af0..7519fec10a 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -143,6 +143,7 @@ module ActiveRecord
def grouped_records(association, records)
h = {}
records.each do |record|
+ next unless record
assoc = record.association(association)
klasses = h[assoc.reflection] ||= {}
(klasses[assoc.klass] ||= []) << record
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 53a9c874bf..47c6f94ba7 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -65,6 +65,8 @@ module ActiveRecord
end
class Type # :nodoc:
+ delegate :type, :type_cast_for_database, to: :@column
+
def initialize(column)
@column = column
end
@@ -77,10 +79,6 @@ module ActiveRecord
end
end
- def type
- @column.type
- end
-
def accessor
ActiveRecord::Store::IndifferentHashAccessor
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index dfebb2cf56..6149ac4906 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -2,6 +2,8 @@ module ActiveRecord
module AttributeMethods
module TimeZoneConversion
class Type # :nodoc:
+ delegate :type, :type_cast_for_database, to: :@column
+
def initialize(column)
@column = column
end
@@ -10,10 +12,6 @@ module ActiveRecord
value = @column.type_cast(value)
value.acts_like?(:time) ? value.in_time_zone : value
end
-
- def type
- @column.type
- end
end
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 1a4d2957ec..2a7acf6787 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -273,9 +273,11 @@ module ActiveRecord
# go through nested autosave associations that are loaded in memory (without loading
# any new ones), and return true if is changed for autosave
def nested_records_changed_for_autosave?
- self.class.reflect_on_all_autosave_associations.any? do |reflection|
- association = association_instance_get(reflection.name)
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ self.class._reflections.values.any? do |reflection|
+ if reflection.options[:autosave]
+ association = association_instance_get(reflection.name)
+ association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index db4d5f0129..8b0fffcf06 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -19,6 +19,7 @@ require 'active_record/errors'
require 'active_record/log_subscriber'
require 'active_record/explain_subscriber'
require 'active_record/relation/delegation'
+require 'active_record/properties'
module ActiveRecord #:nodoc:
# = Active Record
@@ -321,6 +322,7 @@ module ActiveRecord #:nodoc:
include Reflection
include Serialization
include Store
+ include Properties
end
ActiveSupport.run_load_hooks(:active_record, Base)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 75501852ed..f836e60988 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -9,26 +9,22 @@ module ActiveRecord
# records are quoted as their primary key
return value.quoted_id if value.respond_to?(:quoted_id)
+ # FIXME: The only case we get an object other than nil or a real column
+ # is `SchemaStatements#add_column` with a PG array that has a non-empty default
+ # value. Is this really the only case? Are we missing tests for other types?
+ # We should have a real column object passed (or nil) here, and check for that
+ # instead
+ if column.respond_to?(:type_cast_for_database)
+ value = column.type_cast_for_database(value)
+ end
+
case value
when String, ActiveSupport::Multibyte::Chars
- value = value.to_s
- return "'#{quote_string(value)}'" unless column
-
- case column.type
- when :integer then value.to_i.to_s
- when :float then value.to_f.to_s
- else
- "'#{quote_string(value)}'"
- end
-
- when true, false
- if column && column.type == :integer
- value ? '1' : '0'
- else
- value ? quoted_true : quoted_false
- end
- # BigDecimals need to be put in a non-normalized form and quoted.
+ "'#{quote_string(value.to_s)}'"
+ when true then quoted_true
+ when false then quoted_false
when nil then "NULL"
+ # BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
when Numeric, ActiveSupport::Duration then value.to_s
when Date, Time then "'#{quoted_date(value)}'"
@@ -47,30 +43,25 @@ module ActiveRecord
return value.id
end
- case value
- when String, ActiveSupport::Multibyte::Chars
- value = value.to_s
- return value unless column
-
- case column.type
- when :integer then value.to_i
- when :float then value.to_f
- else
- value
- end
+ # FIXME: The only case we get an object other than nil or a real column
+ # is `SchemaStatements#add_column` with a PG array that has a non-empty default
+ # value. Is this really the only case? Are we missing tests for other types?
+ # We should have a real column object passed (or nil) here, and check for that
+ # instead
+ if column.respond_to?(:type_cast_for_database)
+ value = column.type_cast_for_database(value)
+ end
- when true, false
- if column && column.type == :integer
- value ? 1 : 0
- else
- value ? 't' : 'f'
- end
- # BigDecimals need to be put in a non-normalized form and quoted.
- when nil then nil
+ case value
+ when Symbol, ActiveSupport::Multibyte::Chars
+ value.to_s
+ when true then unquoted_true
+ when false then unquoted_false
+ # BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
- when Numeric then value
when Date, Time then quoted_date(value)
- when Symbol then value.to_s
+ when *types_which_need_no_typecasting
+ value
else
to_type = column ? " to #{column.type}" : ""
raise TypeError, "can't cast #{value.class}#{to_type}"
@@ -109,10 +100,18 @@ module ActiveRecord
"'t'"
end
+ def unquoted_true
+ 't'
+ end
+
def quoted_false
"'f'"
end
+ def unquoted_false
+ 'f'
+ end
+
def quoted_date(value)
if value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
@@ -124,6 +123,12 @@ module ActiveRecord
value.to_s(:db)
end
+
+ private
+
+ def types_which_need_no_typecasting
+ [nil, Numeric, String]
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index d3f8470c30..82e62786ca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -178,17 +178,6 @@ module ActiveRecord
true
end
- def type_cast(value, column)
- case value
- when TrueClass
- 1
- when FalseClass
- 0
- else
- super
- end
- end
-
# MySQL 4 technically support transaction isolation, but it is affected by a bug
# where the transaction level gets persisted for the whole session:
#
@@ -234,8 +223,6 @@ module ActiveRecord
if value.kind_of?(String) && column && column.type == :binary
s = value.unpack("H*")[0]
"x'#{s}'"
- elsif value.kind_of?(BigDecimal)
- value.to_s("F")
else
super
end
@@ -253,10 +240,18 @@ module ActiveRecord
QUOTED_TRUE
end
+ def unquoted_true
+ 1
+ end
+
def quoted_false
QUOTED_FALSE
end
+ def unquoted_false
+ 0
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 42aabd6d7f..86232f9d3f 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -18,7 +18,8 @@ module ActiveRecord
alias :encoded? :coder
- delegate :type, :precision, :scale, :limit, :klass, :text?, :number?, :binary?, :type_cast_for_write, to: :cast_type
+ delegate :type, :precision, :scale, :limit, :klass, :text?, :number?, :binary?,
+ :type_cast_for_write, :type_cast_for_database, to: :cast_type
# Instantiates a new column in the table.
#
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 4620206e1a..1f327d1f2f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -119,13 +119,13 @@ module ActiveRecord
ADAPTER_NAME
end
- def schema_creation
+ def schema_creation # :nodoc:
PostgreSQL::SchemaCreation.new self
end
# Adds `:array` option to the default set provided by the
# AbstractAdapter
- def prepare_column_options(column, types)
+ def prepare_column_options(column, types) # :nodoc:
spec = super
spec[:array] = 'true' if column.respond_to?(:array) && column.array
spec[:default] = "\"#{column.default_function}\"" if column.default_function
@@ -406,7 +406,7 @@ module ActiveRecord
private
- def get_oid_type(oid, fmod, column_name, sql_type = '')
+ def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
if !type_map.key?(oid)
load_additional_types(type_map, [oid])
end
@@ -419,7 +419,7 @@ module ActiveRecord
}
end
- def initialize_type_map(m)
+ def initialize_type_map(m) # :nodoc:
register_class_with_limit m, 'int2', OID::Integer
m.alias_type 'int4', 'int2'
m.alias_type 'int8', 'int2'
@@ -496,7 +496,7 @@ module ActiveRecord
end
# Extracts the value from a PostgreSQL column default definition.
- def extract_value_from_default(default)
+ def extract_value_from_default(default) # :nodoc:
# This is a performance optimization for Ruby 1.9.2 in development.
# If the value is nil, we return nil straight away without checking
# the regular expressions. If we check each regular expression,
@@ -558,15 +558,15 @@ module ActiveRecord
end
end
- def extract_default_function(default_value, default)
+ def extract_default_function(default_value, default) # :nodoc:
default if has_default_function?(default_value, default)
end
- def has_default_function?(default_value, default)
+ def has_default_function?(default_value, default) # :nodoc:
!default_value && (%r{\w+\(.*\)} === default)
end
- def load_additional_types(type_map, oids = nil)
+ def load_additional_types(type_map, oids = nil) # :nodoc:
if supports_ranges?
query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
@@ -760,16 +760,6 @@ module ActiveRecord
end_sql
end
- def extract_pg_identifier_from_name(name) # :nodoc:
- match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
-
- if match_data
- rest = name[match_data[0].length, name.length]
- rest = rest[1, rest.length] if rest.start_with? "."
- [match_data[1], (rest.length > 0 ? rest : nil)]
- end
- end
-
def extract_table_ref_from_insert_sql(sql) # :nodoc:
sql[/into\s+([^\(]*).*values\s*\(/im]
$1.strip if $1
diff --git a/activerecord/lib/active_record/connection_adapters/type/binary.rb b/activerecord/lib/active_record/connection_adapters/type/binary.rb
index 4b2d1a66e0..60afe44de1 100644
--- a/activerecord/lib/active_record/connection_adapters/type/binary.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/binary.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class Binary < Value # :nodoc:
+ class Binary < Value
def type
:binary
end
diff --git a/activerecord/lib/active_record/connection_adapters/type/boolean.rb b/activerecord/lib/active_record/connection_adapters/type/boolean.rb
index 2337bdd563..0d97379189 100644
--- a/activerecord/lib/active_record/connection_adapters/type/boolean.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/boolean.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class Boolean < Value # :nodoc:
+ class Boolean < Value
def type
:boolean
end
diff --git a/activerecord/lib/active_record/connection_adapters/type/date.rb b/activerecord/lib/active_record/connection_adapters/type/date.rb
index 1e7205fd0b..e8becbe1f4 100644
--- a/activerecord/lib/active_record/connection_adapters/type/date.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/date.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class Date < Value # :nodoc:
+ class Date < Value
def type
:date
end
diff --git a/activerecord/lib/active_record/connection_adapters/type/date_time.rb b/activerecord/lib/active_record/connection_adapters/type/date_time.rb
index c34f4c5a53..64f5d05301 100644
--- a/activerecord/lib/active_record/connection_adapters/type/date_time.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/date_time.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class DateTime < Value # :nodoc:
+ class DateTime < Value
include TimeValue
def type
diff --git a/activerecord/lib/active_record/connection_adapters/type/decimal.rb b/activerecord/lib/active_record/connection_adapters/type/decimal.rb
index ac5af4b963..e93906ba19 100644
--- a/activerecord/lib/active_record/connection_adapters/type/decimal.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/decimal.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class Decimal < Value # :nodoc:
+ class Decimal < Value
include Numeric
def type
diff --git a/activerecord/lib/active_record/connection_adapters/type/float.rb b/activerecord/lib/active_record/connection_adapters/type/float.rb
index 51cfa5d86a..f2427d2dfa 100644
--- a/activerecord/lib/active_record/connection_adapters/type/float.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/float.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class Float < Value # :nodoc:
+ class Float < Value
include Numeric
def type
@@ -12,6 +12,8 @@ module ActiveRecord
::Float
end
+ alias type_cast_for_database type_cast
+
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/connection_adapters/type/integer.rb b/activerecord/lib/active_record/connection_adapters/type/integer.rb
index 8f3469434c..596f4de2a8 100644
--- a/activerecord/lib/active_record/connection_adapters/type/integer.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/integer.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class Integer < Value # :nodoc:
+ class Integer < Value
include Numeric
def type
@@ -12,6 +12,8 @@ module ActiveRecord
::Fixnum
end
+ alias type_cast_for_database type_cast
+
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/connection_adapters/type/string.rb b/activerecord/lib/active_record/connection_adapters/type/string.rb
index 55f0e1ee1c..471f949e09 100644
--- a/activerecord/lib/active_record/connection_adapters/type/string.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/string.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class String < Value # :nodoc:
+ class String < Value
def type
:string
end
diff --git a/activerecord/lib/active_record/connection_adapters/type/text.rb b/activerecord/lib/active_record/connection_adapters/type/text.rb
index ee5842a3fc..61095ebb38 100644
--- a/activerecord/lib/active_record/connection_adapters/type/text.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/text.rb
@@ -3,7 +3,7 @@ require 'active_record/connection_adapters/type/string'
module ActiveRecord
module ConnectionAdapters
module Type
- class Text < String # :nodoc:
+ class Text < String
def type
:text
end
diff --git a/activerecord/lib/active_record/connection_adapters/type/time.rb b/activerecord/lib/active_record/connection_adapters/type/time.rb
index 4dd201e3fe..bc331b0fa7 100644
--- a/activerecord/lib/active_record/connection_adapters/type/time.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/time.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class Time < Value # :nodoc:
+ class Time < Value
include TimeValue
def type
diff --git a/activerecord/lib/active_record/connection_adapters/type/value.rb b/activerecord/lib/active_record/connection_adapters/type/value.rb
index 54a3e9dd7a..60b443004c 100644
--- a/activerecord/lib/active_record/connection_adapters/type/value.rb
+++ b/activerecord/lib/active_record/connection_adapters/type/value.rb
@@ -1,9 +1,11 @@
module ActiveRecord
module ConnectionAdapters
module Type
- class Value # :nodoc:
+ class Value
attr_reader :precision, :scale, :limit
+ # Valid options are +precision+, +scale+, and +limit+.
+ # They are only used when dumping schema.
def initialize(options = {})
options.assert_valid_keys(:precision, :scale, :limit)
@precision = options[:precision]
@@ -11,8 +13,13 @@ module ActiveRecord
@limit = options[:limit]
end
+ # The simplified that this object represents. Subclasses
+ # should override this method.
def type; end
+ # Takes an input from the database, or from attribute setters,
+ # and casts it to a type appropriate for this object. This method
+ # should not be overriden by subclasses. Instead, override `cast_value`.
def type_cast(value)
cast_value(value) unless value.nil?
end
@@ -21,6 +28,10 @@ module ActiveRecord
value
end
+ def type_cast_for_database(value)
+ type_cast_for_write(value)
+ end
+
def text?
false
end
@@ -39,7 +50,9 @@ module ActiveRecord
private
- def cast_value(value)
+ # Responsible for casting values from external sources to the appropriate
+ # type. Called by `type_cast` for all values except `nil`.
+ def cast_value(value) # :api: public
value
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 71e176a328..05c4b13016 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -20,7 +20,7 @@ module ActiveRecord
def reset_counters(id, *counters)
object = find(id)
counters.each do |counter_association|
- has_many_association = reflect_on_association(counter_association.to_sym)
+ has_many_association = _reflect_on_association(counter_association.to_sym)
unless has_many_association
has_many = reflect_on_all_associations(:has_many)
has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym }
@@ -34,8 +34,7 @@ module ActiveRecord
foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
+ reflection = child_class._reflections.values.find { |e| :belongs_to == e.macro && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
counter_name = reflection.counter_cache_column
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
@@ -167,7 +166,7 @@ module ActiveRecord
end
def each_counter_cached_associations
- reflections.each do |name, reflection|
+ _reflections.each do |name, reflection|
yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 47d32fae05..d40bea5ea7 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -649,7 +649,7 @@ module ActiveRecord
model_class
end
- reflection_class.reflect_on_all_associations.each do |association|
+ reflection_class._reflections.values.each do |association|
case association.macro
when :belongs_to
# Do not replace association name with association foreign key if they are named the same
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 8449fb1266..a4e10ed2e7 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -217,16 +217,6 @@ module ActiveRecord
connection.schema_cache.table_exists?(table_name)
end
- # Returns an array of column objects for the table associated with this class.
- def columns
- connection.schema_cache.columns(table_name)
- end
-
- # Returns a hash of column objects for the table associated with this class.
- def columns_hash
- connection.schema_cache.columns_hash(table_name)
- end
-
def column_types # :nodoc:
@column_types ||= decorate_columns(columns_hash.dup)
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 29ed499b1b..7dc7169a02 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -305,7 +305,7 @@ module ActiveRecord
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
attr_names.each do |association_name|
- if reflection = reflect_on_association(association_name)
+ if reflection = _reflect_on_association(association_name)
reflection.autosave = true
add_autosave_association_callbacks(reflection)
@@ -542,7 +542,7 @@ module ActiveRecord
end
def raise_nested_attributes_record_not_found!(association_name, record_id)
- raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
end
diff --git a/activerecord/lib/active_record/properties.rb b/activerecord/lib/active_record/properties.rb
new file mode 100644
index 0000000000..a5d724de0e
--- /dev/null
+++ b/activerecord/lib/active_record/properties.rb
@@ -0,0 +1,95 @@
+module ActiveRecord
+ module Properties
+ extend ActiveSupport::Concern
+
+ Type = ConnectionAdapters::Type
+
+ module ClassMethods
+ # Defines or overrides a property on this model. This allows customization of
+ # Active Record's type casting behavior, as well as adding support for user defined
+ # types.
+ #
+ # ==== Examples
+ #
+ # The type detected by Active Record can be overriden.
+ #
+ # # db/schema.rb
+ # create_table :store_listings, force: true do |t|
+ # t.decimal :price_in_cents
+ # end
+ #
+ # # app/models/store_listing.rb
+ # class StoreListing < ActiveRecord::Base
+ # end
+ #
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
+ #
+ # # before
+ # store_listing.price_in_cents # => BigDecimal.new(10.1)
+ #
+ # class StoreListing < ActiveRecord::Base
+ # property :price_in_cents, Type::Integer.new
+ # end
+ #
+ # # after
+ # store_listing.price_in_cents # => 10
+ #
+ # Users may also define their own custom types, as long as they respond to the methods
+ # defined on the value type. The `type_cast` method on your type object will be called
+ # with values both from the database, and from your controllers. See
+ # `ActiveRecord::Properties::Type::Value` for the expected API. It is recommended that your
+ # type objects inherit from an existing type, or the base value type.
+ #
+ # class MoneyType < ActiveRecord::Type::Integer
+ # def type_cast(value)
+ # if value.include?('$')
+ # price_in_dollars = value.gsub(/\$/, '').to_f
+ # price_in_dollars * 100
+ # else
+ # value.to_i
+ # end
+ # end
+ # end
+ #
+ # class StoreListing < ActiveRecord::Base
+ # property :price_in_cents, MoneyType.new
+ # end
+ #
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
+ # store_listing.price_in_cents # => 1000
+ def property(name, cast_type)
+ name = name.to_s
+ user_provided_columns[name] = ConnectionAdapters::Column.new(name, nil, cast_type)
+ end
+
+ # Returns an array of column objects for the table associated with this class.
+ def columns
+ @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
+ end
+
+ # Returns a hash of column objects for the table associated with this class.
+ def columns_hash
+ @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
+ end
+
+ def reset_column_information # :nodoc:
+ super
+
+ @columns = nil
+ @columns_hash = nil
+ end
+
+ private
+
+ def user_provided_columns
+ @user_provided_columns ||= {}
+ end
+
+ def add_user_provided_columns(schema_columns)
+ schema_columns.reject { |column|
+ user_provided_columns.key? column.name
+ } + user_provided_columns.values
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0eec6774a0..dd80ec6274 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -6,9 +6,9 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :reflections
+ class_attribute :_reflections
class_attribute :aggregate_reflections
- self.reflections = {}
+ self._reflections = {}
self.aggregate_reflections = {}
end
@@ -24,7 +24,7 @@ module ActiveRecord
end
def self.add_reflection(ar, name, reflection)
- ar.reflections = ar.reflections.merge(name.to_s => reflection)
+ ar._reflections = ar._reflections.merge(name.to_s => reflection)
end
def self.add_aggregate_reflection(ar, name, reflection)
@@ -53,6 +53,24 @@ module ActiveRecord
aggregate_reflections[aggregation.to_s]
end
+ # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
+ #
+ # Account.reflections # => {balance: AggregateReflection}
+ #
+ # @api public
+ def reflections
+ ref = {}
+ _reflections.each do |name, reflection|
+ parent_name, parent_reflection = reflection.parent_reflection
+ if parent_name
+ ref[parent_name] = parent_reflection
+ else
+ ref[name] = reflection
+ end
+ end
+ ref
+ end
+
# Returns an array of AssociationReflection objects for all the
# associations in the class. If you only want to reflect on a certain
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
@@ -63,6 +81,7 @@ module ActiveRecord
# Account.reflect_on_all_associations # returns an array of all associations
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
#
+ # @api public
def reflect_on_all_associations(macro = nil)
association_reflections = reflections.values
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
@@ -73,11 +92,19 @@ module ActiveRecord
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
+ # @api public
def reflect_on_association(association)
reflections[association.to_s]
end
+ # @api private
+ def _reflect_on_association(association) #:nodoc:
+ _reflections[association.to_s]
+ end
+
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
+ #
+ # @api public
def reflect_on_all_autosave_associations
reflections.values.select { |reflection| reflection.options[:autosave] }
end
@@ -129,6 +156,10 @@ module ActiveRecord
def autosave=(autosave)
@automatic_inverse_of = false
@options[:autosave] = autosave
+ _, parent_reflection = self.parent_reflection
+ if parent_reflection
+ parent_reflection.autosave = autosave
+ end
end
# Returns the class for the macro.
@@ -193,10 +224,11 @@ module ActiveRecord
end
attr_reader :type, :foreign_type
+ attr_accessor :parent_reflection # [:name, Reflection]
def initialize(macro, name, scope, options, active_record)
super
- @collection = :has_many == macro
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
@automatic_inverse_of = nil
@type = options[:as] && "#{options[:as]}_type"
@foreign_type = options[:foreign_type] || "#{name}_type"
@@ -330,12 +362,12 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
def inverse_of
return unless inverse_name
- @inverse_of ||= klass.reflect_on_association inverse_name
+ @inverse_of ||= klass._reflect_on_association inverse_name
end
def polymorphic_inverse_of(associated_class)
if has_inverse?
- if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
+ if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
inverse_relationship
else
raise InverseOfAssociationNotFoundError.new(self, associated_class)
@@ -436,7 +468,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym
begin
- reflection = klass.reflect_on_association(inverse_name)
+ reflection = klass._reflect_on_association(inverse_name)
rescue NameError
# Give up: we couldn't compute the klass type so we won't be able
# to find any associations either.
@@ -535,7 +567,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
#
def source_reflection
- through_reflection.klass.reflect_on_association(source_reflection_name)
+ through_reflection.klass._reflect_on_association(source_reflection_name)
end
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -551,7 +583,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
#
def through_reflection
- active_record.reflect_on_association(options[:through])
+ active_record._reflect_on_association(options[:through])
end
# Returns an array of reflections which are involved in this association. Each item in the
@@ -658,7 +690,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
names = names.find_all { |n|
- through_reflection.klass.reflect_on_association(n)
+ through_reflection.klass._reflect_on_association(n)
}
if names.length > 1
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 56cf9bcd27..d155517b18 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -277,7 +277,7 @@ module ActiveRecord
group_attrs = group_values
if group_attrs.first.respond_to?(:to_sym)
- association = @klass.reflect_on_association(group_attrs.first.to_sym)
+ association = @klass._reflect_on_association(group_attrs.first.to_sym)
associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
group_fields = Array(associated ? association.foreign_key : group_attrs)
else
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index d40f276968..eff5c8f09c 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -26,7 +26,7 @@ module ActiveRecord
queries << '1=0'
else
table = Arel::Table.new(column, default_table.engine)
- association = klass.reflect_on_association(column.to_sym)
+ association = klass._reflect_on_association(column.to_sym)
value.each do |k, v|
queries.concat expand(association && association.klass, table, k, v)
@@ -55,7 +55,7 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if klass && reflection = klass.reflect_on_association(column.to_sym)
+ if klass && reflection = klass._reflect_on_association(column.to_sym)
if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
queries << build(table[reflection.foreign_type], base_class)
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 2f6c34ac08..78dba8be06 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -2,28 +2,33 @@ module ActiveRecord
class PredicateBuilder
class ArrayHandler # :nodoc:
def call(attribute, value)
+ return attribute.in([]) if value.empty?
+
values = value.map { |x| x.is_a?(Base) ? x.id : x }
ranges, values = values.partition { |v| v.is_a?(Range) }
+ nils, values = values.partition(&:nil?)
- values_predicate = if values.include?(nil)
- values = values.compact
-
+ values_predicate =
case values.length
- when 0
- attribute.eq(nil)
- when 1
- attribute.eq(values.first).or(attribute.eq(nil))
- else
- attribute.in(values).or(attribute.eq(nil))
+ when 0 then NullPredicate
+ when 1 then attribute.eq(values.first)
+ else attribute.in(values)
end
- else
- attribute.in(values)
+
+ unless nils.empty?
+ values_predicate = values_predicate.or(attribute.eq(nil))
end
array_predicates = ranges.map { |range| attribute.in(range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
end
+
+ module NullPredicate
+ def self.or(other)
+ other
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 9a19483da3..e586744818 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -4,7 +4,7 @@ module ActiveRecord
def validate(record)
super
attributes.each do |attribute|
- next unless record.class.reflect_on_association(attribute)
+ next unless record.class._reflect_on_association(attribute)
associated_records = Array.wrap(record.send(attribute))
# Superclass validates presence. Ensure present records aren't about to be destroyed.
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index ee080451a9..b6fccc9b94 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -47,7 +47,7 @@ module ActiveRecord
end
def build_relation(klass, table, attribute, value) #:nodoc:
- if reflection = klass.reflect_on_association(attribute)
+ if reflection = klass._reflect_on_association(attribute)
attribute = reflection.foreign_key
value = value.attributes[reflection.primary_key_column.name] unless value.nil?
end
@@ -74,7 +74,7 @@ module ActiveRecord
def scope_relation(record, table, relation)
Array(options[:scope]).each do |scope_item|
- if reflection = record.class.reflect_on_association(scope_item)
+ if reflection = record.class._reflect_on_association(scope_item)
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
else
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index 1f55cce352..972abf7cdc 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -122,7 +122,6 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
assert_equal "Champs-Élysées", composite.address.street
composite.address = FullAddress.new("Paris", "Rue Basse")
- skip "Saving with custom OID type is currently not supported."
composite.save!
assert_equal 'Paris', composite.reload.address.city
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 0c4f06d6a9..209b7f70c9 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -15,10 +15,10 @@ module ActiveRecord
def test_type_cast_binary_encoding_without_logger
@conn.extend(Module.new { def logger; end })
- column = Struct.new(:type, :name).new(:string, "foo")
+ cast_type = Type::String.new
binary = SecureRandom.hex
expected = binary.dup.encode!(Encoding::UTF_8)
- assert_equal expected, @conn.type_cast(binary, column)
+ assert_equal expected, @conn.type_cast(binary, cast_type)
end
def test_type_cast_symbol
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index ebcf453305..4bd4486b41 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1167,6 +1167,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
)
end
+ test "deep preload" do
+ post = Post.preload(author: :posts, comments: :post).first
+
+ assert_predicate post.author.association(:posts), :loaded?
+ assert_predicate post.comments.first.association(:post), :loaded?
+ end
+
test "preloading does not cache has many association subset when preloaded with a through association" do
author = Author.includes(:comments_with_order_and_conditions, :posts).first
assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size }
@@ -1196,6 +1203,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ test "preloading the same association twice works" do
+ Member.create!
+ members = Member.preload(:current_membership).includes(current_membership: :club).all.to_a
+ assert_no_queries {
+ members_with_membership = members.select(&:current_membership)
+ assert_equal 3, members_with_membership.map(&:current_membership).map(&:club).size
+ }
+ end
+
test "preloading with a polymorphic association and using the existential predicate" do
assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index c565daba65..1b3aa84a06 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -518,6 +518,10 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Topic.find('1-meowmeow'), Topic.find(1)
end
+ def test_find_by_slug_with_array
+ assert_equal Topic.find(['1-meowmeow', '2-hello']), Topic.find([1, 2])
+ end
+
def test_equality_of_new_records
assert_not_equal Topic.new, Topic.new
assert_equal false, Topic.new == Topic.new
diff --git a/activerecord/test/cases/custom_properties_test.rb b/activerecord/test/cases/custom_properties_test.rb
new file mode 100644
index 0000000000..9598f0299c
--- /dev/null
+++ b/activerecord/test/cases/custom_properties_test.rb
@@ -0,0 +1,64 @@
+require 'cases/helper'
+
+class OverloadedType < ActiveRecord::Base
+ property :overloaded_float, Type::Integer.new
+ property :overloaded_string_with_limit, Type::String.new(limit: 50)
+ property :non_existent_decimal, Type::Decimal.new
+end
+
+class UnoverloadedType < ActiveRecord::Base
+ self.table_name = 'overloaded_types'
+end
+
+module ActiveRecord
+ class CustomPropertiesTest < ActiveRecord::TestCase
+ def test_overloading_types
+ data = OverloadedType.new
+
+ data.overloaded_float = "1.1"
+ data.unoverloaded_float = "1.1"
+
+ assert_equal 1, data.overloaded_float
+ assert_equal 1.1, data.unoverloaded_float
+ end
+
+ def test_overloaded_properties_save
+ data = OverloadedType.new
+
+ data.overloaded_float = "2.2"
+ data.save!
+ data.reload
+
+ assert_equal 2, data.overloaded_float
+ assert_equal 2.0, UnoverloadedType.last.overloaded_float
+ end
+
+ def test_properties_assigned_in_constructor
+ data = OverloadedType.new(overloaded_float: '3.3')
+
+ assert_equal 3, data.overloaded_float
+ end
+
+ def test_overloaded_properties_with_limit
+ assert_equal 50, OverloadedType.columns_hash['overloaded_string_with_limit'].limit
+ assert_equal 255, UnoverloadedType.columns_hash['overloaded_string_with_limit'].limit
+ end
+
+ def test_nonexistent_property
+ data = OverloadedType.new(non_existent_decimal: 1)
+
+ assert_equal BigDecimal.new(1), data.non_existent_decimal
+ assert_raise ActiveRecord::UnknownAttributeError do
+ UnoverloadedType.new(non_existent_decimal: 1)
+ end
+ end
+
+ def test_overloaded_properties_have_no_default
+ data = OverloadedType.new
+ unoverloaded_data = UnoverloadedType.new
+
+ assert_nil data.overloaded_float
+ assert unoverloaded_data.overloaded_float
+ end
+ end
+end
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index e2439b9a24..bbd5298da1 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -3,14 +3,6 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class QuotingTest < ActiveRecord::TestCase
- class FakeColumn < ActiveRecord::ConnectionAdapters::Column
- attr_accessor :type
-
- def initialize type
- @type = type
- end
- end
-
def setup
@quoter = Class.new { include Quoting }.new
end
@@ -101,12 +93,12 @@ module ActiveRecord
def test_quote_true
assert_equal @quoter.quoted_true, @quoter.quote(true, nil)
- assert_equal '1', @quoter.quote(true, Struct.new(:type).new(:integer))
+ assert_equal '1', @quoter.quote(true, Type::Integer.new)
end
def test_quote_false
assert_equal @quoter.quoted_false, @quoter.quote(false, nil)
- assert_equal '0', @quoter.quote(false, Struct.new(:type).new(:integer))
+ assert_equal '0', @quoter.quote(false, Type::Integer.new)
end
def test_quote_float
@@ -166,26 +158,26 @@ module ActiveRecord
end
def test_quote_string_int_column
- assert_equal "1", @quoter.quote('1', FakeColumn.new(:integer))
- assert_equal "1", @quoter.quote('1.2', FakeColumn.new(:integer))
+ assert_equal "1", @quoter.quote('1', Type::Integer.new)
+ assert_equal "1", @quoter.quote('1.2', Type::Integer.new)
end
def test_quote_string_float_column
- assert_equal "1.0", @quoter.quote('1', FakeColumn.new(:float))
- assert_equal "1.2", @quoter.quote('1.2', FakeColumn.new(:float))
+ assert_equal "1.0", @quoter.quote('1', Type::Float.new)
+ assert_equal "1.2", @quoter.quote('1.2', Type::Float.new)
end
def test_quote_as_mb_chars_binary_column
string = ActiveSupport::Multibyte::Chars.new('lo\l')
- assert_equal "'lo\\\\l'", @quoter.quote(string, FakeColumn.new(:binary))
+ assert_equal "'lo\\\\l'", @quoter.quote(string, Type::Binary.new)
end
def test_quote_binary_without_string_to_binary
- assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:binary))
+ assert_equal "'lo\\\\l'", @quoter.quote('lo\l', Type::Binary.new)
end
def test_string_with_crazy_column
- assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo))
+ assert_equal "'lo\\\\l'", @quoter.quote('lo\l')
end
def test_quote_duration
@@ -193,7 +185,7 @@ module ActiveRecord
end
def test_quote_duration_int_column
- assert_equal "7200", @quoter.quote(2.hours, FakeColumn.new(:integer))
+ assert_equal "7200", @quoter.quote(2.hours, Type::Integer.new)
end
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index c085fcf161..e6603f28be 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -200,7 +200,12 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_should_not_raise_error_when_compared_to_other_object
- assert_nothing_raised { Firm.reflections['clients'] == Object.new }
+ assert_not_equal Object.new, Firm._reflections['clients']
+ end
+
+ def test_has_and_belongs_to_many_reflection
+ assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro
+ assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name
end
def test_has_many_through_reflection
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 4b146c11bc..88df997a2f 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -552,13 +552,6 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_deep_preload
- post = Post.preload(author: :posts, comments: :post).first
-
- assert_predicate post.author.association(:posts), :loaded?
- assert_predicate post.comments.first.association(:post), :loaded?
- end
-
def test_preload_applies_to_all_chained_preloaded_scopes
assert_queries(3) do
post = Post.with_comments.with_tags.first
@@ -750,6 +743,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [], relation.to_a
end
+ def test_typecasting_where_with_array
+ ids = Author.pluck(:id)
+ slugs = ids.map { |id| "#{id}-as-a-slug" }
+
+ assert_equal Author.all.to_a, Author.where(id: slugs).to_a
+ end
+
def test_find_all_using_where_with_relation
david = authors(:david)
# switching the lines below would succeed in current rails
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index c15ee5022e..4cce58f4f4 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -855,6 +855,12 @@ ActiveRecord::Schema.define do
execute "ALTER TABLE lessons_students ADD CONSTRAINT student_id_fk FOREIGN KEY (#{quote_column_name 'student_id'}) REFERENCES #{quote_table_name 'students'} (#{quote_column_name 'id'})"
end
+
+ create_table :overloaded_types, force: true do |t|
+ t.float :overloaded_float, default: 500
+ t.float :unoverloaded_float
+ t.string :overloaded_string_with_limit, limit: 255
+ end
end
Course.connection.create_table :courses, force: true do |t|
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 23cd6716e3..a45f009859 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -1,4 +1,3 @@
-require "active_support"
require "active_support/file_update_checker"
require "active_support/core_ext/array/wrap"
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 133aa6a054..6d09ad4bd4 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -1,4 +1,3 @@
-require "active_support"
require "active_support/i18n_railtie"
module ActiveSupport
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index 92a593965e..ea2d3391bd 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -1,5 +1,3 @@
-require 'active_support'
-
module ActiveSupport
autoload :Duration, 'active_support/duration'
autoload :TimeWithZone, 'active_support/time_with_zone'
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 5a6e8add6c..4cd4a9d70c 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Change Posts to Articles in Getting Started sample application in order to
+better align with the actual guides.
+
+ * John Kelly Ferguson*
+
* Update all Rails 4.1.0 references to 4.1.1 within the guides and code.
* John Kelly Ferguson*
@@ -5,7 +10,7 @@
* Split up rows in the Explain Queries table of the ActiveRecord Querying section
in order to improve readability.
- * John Kelly Ferguson *
+ * John Kelly Ferguson*
* Change all non-HTTP method 'post' references to 'article'.
diff --git a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee b/guides/code/getting_started/app/assets/javascripts/articles.js.coffee
index 24f83d18bb..24f83d18bb 100644
--- a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee
+++ b/guides/code/getting_started/app/assets/javascripts/articles.js.coffee
diff --git a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss b/guides/code/getting_started/app/assets/stylesheets/articles.css.scss
index 1a7e15390c..cca548710d 100644
--- a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss
+++ b/guides/code/getting_started/app/assets/stylesheets/articles.css.scss
@@ -1,3 +1,3 @@
-// Place all the styles related to the posts controller here.
+// Place all the styles related to the articles controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/guides/code/getting_started/app/controllers/articles_controller.rb b/guides/code/getting_started/app/controllers/articles_controller.rb
new file mode 100644
index 0000000000..275b84e8b7
--- /dev/null
+++ b/guides/code/getting_started/app/controllers/articles_controller.rb
@@ -0,0 +1,53 @@
+class ArticlesController < ApplicationController
+
+ http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
+
+ def index
+ @articles = Article.all
+ end
+
+ def show
+ @article = Article.find(params[:id])
+ end
+
+ def edit
+ @article = Article.find(params[:id])
+ end
+
+ def update
+ @article = Article.find(params[:id])
+
+ if @article.update(article_params)
+ redirect_to action: :show, id: @article.id
+ else
+ render 'edit'
+ end
+ end
+
+ def new
+ @article = Article.new
+ end
+
+ def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ redirect_to action: :show, id: @article.id
+ else
+ render 'new'
+ end
+ end
+
+ def destroy
+ @article = Article.find(params[:id])
+ @article.destroy
+
+ redirect_to action: :index
+ end
+
+ private
+
+ def article_params
+ params.require(:article).permit(:title, :text)
+ end
+end
diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb
index b2d9bcdf7f..61813b1003 100644
--- a/guides/code/getting_started/app/controllers/comments_controller.rb
+++ b/guides/code/getting_started/app/controllers/comments_controller.rb
@@ -3,16 +3,16 @@ class CommentsController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
def create
- @post = Post.find(params[:post_id])
- @comment = @post.comments.create(comment_params)
- redirect_to post_path(@post)
+ @article = Article.find(params[:article_id])
+ @comment = @article.comments.create(comment_params)
+ redirect_to article_path(@article)
end
def destroy
- @post = Post.find(params[:post_id])
- @comment = @post.comments.find(params[:id])
+ @article = Article.find(params[:article_id])
+ @comment = @article.comments.find(params[:id])
@comment.destroy
- redirect_to post_path(@post)
+ redirect_to article_path(@article)
end
private
diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb
deleted file mode 100644
index 02689ad67b..0000000000
--- a/guides/code/getting_started/app/controllers/posts_controller.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-class PostsController < ApplicationController
-
- http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
-
- def index
- @posts = Post.all
- end
-
- def show
- @post = Post.find(params[:id])
- end
-
- def edit
- @post = Post.find(params[:id])
- end
-
- def update
- @post = Post.find(params[:id])
-
- if @post.update(post_params)
- redirect_to action: :show, id: @post.id
- else
- render 'edit'
- end
- end
-
- def new
- @post = Post.new
- end
-
- def create
- @post = Post.new(post_params)
-
- if @post.save
- redirect_to action: :show, id: @post.id
- else
- render 'new'
- end
- end
-
- def destroy
- @post = Post.find(params[:id])
- @post.destroy
-
- redirect_to action: :index
- end
-
- private
-
- def post_params
- params.require(:post).permit(:title, :text)
- end
-end
diff --git a/guides/code/getting_started/app/helpers/articles_helper.rb b/guides/code/getting_started/app/helpers/articles_helper.rb
new file mode 100644
index 0000000000..2968277595
--- /dev/null
+++ b/guides/code/getting_started/app/helpers/articles_helper.rb
@@ -0,0 +1,2 @@
+module ArticlesHelper
+end
diff --git a/guides/code/getting_started/app/helpers/posts_helper.rb b/guides/code/getting_started/app/helpers/posts_helper.rb
deleted file mode 100644
index a7b8cec898..0000000000
--- a/guides/code/getting_started/app/helpers/posts_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module PostsHelper
-end
diff --git a/guides/code/getting_started/app/models/post.rb b/guides/code/getting_started/app/models/article.rb
index 64e0d721fd..6fc7888be2 100644
--- a/guides/code/getting_started/app/models/post.rb
+++ b/guides/code/getting_started/app/models/article.rb
@@ -1,6 +1,6 @@
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
has_many :comments, dependent: :destroy
-
+
validates :title,
presence: true,
length: { minimum: 5 }
diff --git a/guides/code/getting_started/app/models/comment.rb b/guides/code/getting_started/app/models/comment.rb
index 4e76c5b5b0..e2646a324f 100644
--- a/guides/code/getting_started/app/models/comment.rb
+++ b/guides/code/getting_started/app/models/comment.rb
@@ -1,3 +1,3 @@
class Comment < ActiveRecord::Base
- belongs_to :post
+ belongs_to :article
end
diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/articles/_form.html.erb
index f2f83585e1..87e3353ed2 100644
--- a/guides/code/getting_started/app/views/posts/_form.html.erb
+++ b/guides/code/getting_started/app/views/articles/_form.html.erb
@@ -1,10 +1,10 @@
-<%= form_for @post do |f| %>
- <% if @post.errors.any? %>
+<%= form_for @article do |f| %>
+ <% if @article.errors.any? %>
<div id="error_explanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited
- this post from being saved:</h2>
+ <h2><%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:</h2>
<ul>
- <% @post.errors.full_messages.each do |msg| %>
+ <% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
diff --git a/guides/code/getting_started/app/views/posts/edit.html.erb b/guides/code/getting_started/app/views/articles/edit.html.erb
index 393e7430d0..14236e2a98 100644
--- a/guides/code/getting_started/app/views/posts/edit.html.erb
+++ b/guides/code/getting_started/app/views/articles/edit.html.erb
@@ -1,5 +1,5 @@
-<h1>Edit post</h1>
-
+<h1>Edit article</h1>
+
<%= render 'form' %>
-
+
<%= link_to 'Back', action: :index %>
diff --git a/guides/code/getting_started/app/views/articles/index.html.erb b/guides/code/getting_started/app/views/articles/index.html.erb
new file mode 100644
index 0000000000..80e9c8c60c
--- /dev/null
+++ b/guides/code/getting_started/app/views/articles/index.html.erb
@@ -0,0 +1,21 @@
+<h1>Listing Articles</h1>
+<table>
+ <tr>
+ <th>Title</th>
+ <th>Text</th>
+ <th></th>
+ <th></th>
+ <th></th>
+ </tr>
+
+<% @articles.each do |article| %>
+ <tr>
+ <td><%= article.title %></td>
+ <td><%= article.text %></td>
+ <td><%= link_to 'Show', action: :show, id: article.id %></td>
+ <td><%= link_to 'Edit', action: :edit, id: article.id %></td>
+ <td><%= link_to 'Destroy', { action: :destroy, id: article.id },
+ method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ </tr>
+<% end %>
+</table>
diff --git a/guides/code/getting_started/app/views/posts/new.html.erb b/guides/code/getting_started/app/views/articles/new.html.erb
index efa81038ec..652b1c9c0b 100644
--- a/guides/code/getting_started/app/views/posts/new.html.erb
+++ b/guides/code/getting_started/app/views/articles/new.html.erb
@@ -1,5 +1,5 @@
-<h1>New post</h1>
-
+<h1>New article</h1>
+
<%= render 'form' %>
-
+
<%= link_to 'Back', action: :index %>
diff --git a/guides/code/getting_started/app/views/articles/show.html.erb b/guides/code/getting_started/app/views/articles/show.html.erb
new file mode 100644
index 0000000000..6959c80bdb
--- /dev/null
+++ b/guides/code/getting_started/app/views/articles/show.html.erb
@@ -0,0 +1,18 @@
+<p>
+ <strong>Title:</strong>
+ <%= @article.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @article.text %>
+</p>
+
+<h2>Comments</h2>
+<%= render @article.comments %>
+
+<h2>Add a comment:</h2>
+<%= render "comments/form" %>
+
+<%= link_to 'Edit Article', edit_article_path(@article) %> |
+<%= link_to 'Back to Articles', articles_path %>
diff --git a/guides/code/getting_started/app/views/comments/_comment.html.erb b/guides/code/getting_started/app/views/comments/_comment.html.erb
index 593493339e..f7cbfaebfa 100644
--- a/guides/code/getting_started/app/views/comments/_comment.html.erb
+++ b/guides/code/getting_started/app/views/comments/_comment.html.erb
@@ -2,14 +2,14 @@
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
-
+
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<p>
- <%= link_to 'Destroy Comment', [comment.post, comment],
+ <%= link_to 'Destroy Comment', [comment.article, comment],
method: :delete,
data: { confirm: 'Are you sure?' } %>
</p>
diff --git a/guides/code/getting_started/app/views/comments/_form.html.erb b/guides/code/getting_started/app/views/comments/_form.html.erb
index 00cb3a08f0..5850c41a17 100644
--- a/guides/code/getting_started/app/views/comments/_form.html.erb
+++ b/guides/code/getting_started/app/views/comments/_form.html.erb
@@ -1,4 +1,4 @@
-<%= form_for([@post, @post.comments.build]) do |f| %>
+<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
diff --git a/guides/code/getting_started/app/views/posts/index.html.erb b/guides/code/getting_started/app/views/posts/index.html.erb
deleted file mode 100644
index 7369f0396f..0000000000
--- a/guides/code/getting_started/app/views/posts/index.html.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-<h1>Listing Posts</h1>
-<table>
- <tr>
- <th>Title</th>
- <th>Text</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
-
-<% @posts.each do |post| %>
- <tr>
- <td><%= post.title %></td>
- <td><%= post.text %></td>
- <td><%= link_to 'Show', action: :show, id: post.id %></td>
- <td><%= link_to 'Edit', action: :edit, id: post.id %></td>
- <td><%= link_to 'Destroy', { action: :destroy, id: post.id },
- method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
-<% end %>
-</table>
diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb
deleted file mode 100644
index e99e9edbb3..0000000000
--- a/guides/code/getting_started/app/views/posts/show.html.erb
+++ /dev/null
@@ -1,18 +0,0 @@
-<p>
- <strong>Title:</strong>
- <%= @post.title %>
-</p>
-
-<p>
- <strong>Text:</strong>
- <%= @post.text %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %>
diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb
index 56be8dd3cc..1cabd0d217 100644
--- a/guides/code/getting_started/app/views/welcome/index.html.erb
+++ b/guides/code/getting_started/app/views/welcome/index.html.erb
@@ -1,4 +1,4 @@
<h1>Hello, Rails!</h1>
-<%= link_to "My Blog", controller: "posts" %>
-<%= link_to "New Post", new_post_path %>
+<%= link_to "My Blog", controller: "articles" %>
+<%= link_to "New Article", new_article_path %>
diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb
index 65d273b58d..97abca99b9 100644
--- a/guides/code/getting_started/config/routes.rb
+++ b/guides/code/getting_started/config/routes.rb
@@ -1,5 +1,5 @@
Rails.application.routes.draw do
- resources :posts do
+ resources :articles do
resources :comments
end
diff --git a/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb b/guides/code/getting_started/db/migrate/20130122042648_create_articles.rb
index 602bef31ab..6bb255e89f 100644
--- a/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb
+++ b/guides/code/getting_started/db/migrate/20130122042648_create_articles.rb
@@ -1,6 +1,6 @@
-class CreatePosts < ActiveRecord::Migration
+class CreateArticles < ActiveRecord::Migration
def change
- create_table :posts do |t|
+ create_table :articles do |t|
t.string :title
t.text :text
diff --git a/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb b/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb
index 3e51f9c0f7..1f765839ac 100644
--- a/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb
+++ b/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb
@@ -3,7 +3,7 @@ class CreateComments < ActiveRecord::Migration
create_table :comments do |t|
t.string :commenter
t.text :body
- t.references :post, index: true
+ t.references :article, index: true
t.timestamps
end
diff --git a/guides/code/getting_started/db/schema.rb b/guides/code/getting_started/db/schema.rb
index 101fe712a1..be40f7cb0e 100644
--- a/guides/code/getting_started/db/schema.rb
+++ b/guides/code/getting_started/db/schema.rb
@@ -13,21 +13,21 @@
ActiveRecord::Schema.define(version: 20130122045842) do
- create_table "comments", force: true do |t|
- t.string "commenter"
- t.text "body"
- t.integer "post_id"
+ create_table "articles", force: true do |t|
+ t.string "title"
+ t.text "text"
t.datetime "created_at"
t.datetime "updated_at"
end
- add_index "comments", ["post_id"], name: "index_comments_on_post_id"
-
- create_table "posts", force: true do |t|
- t.string "title"
- t.text "text"
+ create_table "comments", force: true do |t|
+ t.string "commenter"
+ t.text "body"
+ t.integer "article_id"
t.datetime "created_at"
t.datetime "updated_at"
end
+ add_index "comments", ["article_id"], name: "index_comments_on_article_id"
+
end
diff --git a/guides/code/getting_started/test/controllers/posts_controller_test.rb b/guides/code/getting_started/test/controllers/articles_controller_test.rb
index 7a6ee4f1db..361aa0f47f 100644
--- a/guides/code/getting_started/test/controllers/posts_controller_test.rb
+++ b/guides/code/getting_started/test/controllers/articles_controller_test.rb
@@ -1,6 +1,6 @@
require 'test_helper'
-class PostsControllerTest < ActionController::TestCase
+class ArticlesControllerTest < ActionController::TestCase
# test "the truth" do
# assert true
# end
diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/articles.yml
index 46b01c3bb4..46b01c3bb4 100644
--- a/guides/code/getting_started/test/fixtures/posts.yml
+++ b/guides/code/getting_started/test/fixtures/articles.yml
diff --git a/guides/code/getting_started/test/fixtures/comments.yml b/guides/code/getting_started/test/fixtures/comments.yml
index 9e409d8a61..05ad26f051 100644
--- a/guides/code/getting_started/test/fixtures/comments.yml
+++ b/guides/code/getting_started/test/fixtures/comments.yml
@@ -3,9 +3,9 @@
one:
commenter: MyString
body: MyText
- post_id:
+ article_id:
two:
commenter: MyString
body: MyText
- post_id:
+ article_id:
diff --git a/guides/code/getting_started/test/helpers/articles_helper_test.rb b/guides/code/getting_started/test/helpers/articles_helper_test.rb
new file mode 100644
index 0000000000..b341344067
--- /dev/null
+++ b/guides/code/getting_started/test/helpers/articles_helper_test.rb
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class ArticlesHelperTest < ActionView::TestCase
+end
diff --git a/guides/code/getting_started/test/helpers/posts_helper_test.rb b/guides/code/getting_started/test/helpers/posts_helper_test.rb
deleted file mode 100644
index 48549c2ea1..0000000000
--- a/guides/code/getting_started/test/helpers/posts_helper_test.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'test_helper'
-
-class PostsHelperTest < ActionView::TestCase
-end
diff --git a/guides/code/getting_started/test/models/post_test.rb b/guides/code/getting_started/test/models/article_test.rb
index 6d9d463a71..11c8abe5f4 100644
--- a/guides/code/getting_started/test/models/post_test.rb
+++ b/guides/code/getting_started/test/models/article_test.rb
@@ -1,6 +1,6 @@
require 'test_helper'
-class PostTest < ActiveSupport::TestCase
+class ArticleTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index d3a96daf7b..133ef58fd6 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -215,6 +215,36 @@ Rails follows a simple set of coding style conventions:
The above are guidelines - please use your best judgment in using them.
+### Benchmark Your Code
+
+If your change has an impact on the performance of Rails, please use the
+[benchmark-ips](https://github.com/evanphx/benchmark-ips) gem to provide
+benchmark results for comparison.
+
+Here's an example of using benchmark-ips:
+
+```ruby
+require 'benchmark/ips'
+
+Benchmark.ips do |x|
+ x.report('addition') { 1 + 2 }
+ x.report('addition with send') { 1.send(:+, 2) }
+end
+```
+
+This will generate a report with the following information:
+
+```
+Calculating -------------------------------------
+ addition 69114 i/100ms
+ addition with send 64062 i/100ms
+-------------------------------------------------
+ addition 5307644.4 (±3.5%) i/s - 26539776 in 5.007219s
+ addition with send 3702897.9 (±3.5%) i/s - 18513918 in 5.006723s
+```
+
+Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information.
+
### Running Tests
It is not customary in Rails to run the full test suite before pushing
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 9e7569465f..1343f0a628 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Replace double quotes with single quotes while adding an entry into Gemfile.
+
+ *Alexander Belaev*
+
* Default `config.assets.digest` to `true` in development.
*Dan Kang*
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index dce734b54e..04ce38f841 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -156,7 +156,8 @@ module Rails
args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
klass.start(args, config)
else
- puts "Could not find generator #{namespace}."
+ puts "Could not find generator '#{namespace}'. Please choose a generator below."
+ print_generators
end
end
@@ -199,17 +200,6 @@ module Rails
# Show help message with available generators.
def self.help(command = 'generate')
- lookup!
-
- namespaces = subclasses.map{ |k| k.namespace }
- namespaces.sort!
-
- groups = Hash.new { |h,k| h[k] = [] }
- namespaces.each do |namespace|
- base = namespace.split(':').first
- groups[base] << namespace
- end
-
puts "Usage: rails #{command} GENERATOR [args] [options]"
puts
puts "General options:"
@@ -222,6 +212,20 @@ module Rails
puts "Please choose a generator below."
puts
+ print_generators
+ end
+
+ def self.print_generators
+ lookup!
+
+ namespaces = subclasses.map{ |k| k.namespace }
+ namespaces.sort!
+
+ groups = Hash.new { |h,k| h[k] = [] }
+ namespaces.each do |namespace|
+ base = namespace.split(':').first
+ groups[base] << namespace
+ end
# Print Rails defaults first.
rails = groups.delete("rails")
rails.map! { |n| n.sub(/^rails:/, '') }
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 625f031c94..a239874df0 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -20,9 +20,9 @@ module Rails
# Set the message to be shown in logs. Uses the git repo if one is given,
# otherwise use name (version).
- parts, message = [ name.inspect ], name
+ parts, message = [ quote(name) ], name
if version ||= options.delete(:version)
- parts << version.inspect
+ parts << quote(version)
message << " (#{version})"
end
message = options[:git] if options[:git]
@@ -30,7 +30,7 @@ module Rails
log :gemfile, message
options.each do |option, value|
- parts << "#{option}: #{value.inspect}"
+ parts << "#{option}: #{quote(value)}"
end
in_root do
@@ -68,7 +68,7 @@ module Rails
log :source, source
in_root do
- prepend_file "Gemfile", "source #{source.inspect}\n", verbose: false
+ prepend_file "Gemfile", "source #{quote(source)}\n", verbose: false
end
end
@@ -255,6 +255,15 @@ module Rails
end
end
+ # Surround string with single quotes if there is no quotes.
+ # Otherwise fall back to double quotes
+ def quote(str)
+ if str.include?("'")
+ str.inspect
+ else
+ "'#{str}'"
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb
index 3b7f358a5b..df74643a59 100644
--- a/railties/lib/rails/ruby_version_check.rb
+++ b/railties/lib/rails/ruby_version_check.rb
@@ -2,7 +2,7 @@ if RUBY_VERSION < '1.9.3'
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
abort <<-end_message
- Rails 4 prefers to run on Ruby 2.0.
+ Rails 4 prefers to run on Ruby 2.1 or newer.
You're running
#{desc}
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 0db40c1d32..6d6de0fb52 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -41,13 +41,13 @@ class ActionsTest < Rails::Generators::TestCase
def test_add_source_adds_source_to_gemfile
run_generator
action :add_source, 'http://gems.github.com'
- assert_file 'Gemfile', /source "http:\/\/gems\.github\.com"/
+ assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com'/
end
def test_gem_should_put_gem_dependency_in_gemfile
run_generator
action :gem, 'will-paginate'
- assert_file 'Gemfile', /gem "will\-paginate"/
+ assert_file 'Gemfile', /gem 'will\-paginate'/
end
def test_gem_with_version_should_include_version_in_gemfile
@@ -55,7 +55,7 @@ class ActionsTest < Rails::Generators::TestCase
action :gem, 'rspec', '>=2.0.0.a5'
- assert_file 'Gemfile', /gem "rspec", ">=2.0.0.a5"/
+ assert_file 'Gemfile', /gem 'rspec', '>=2.0.0.a5'/
end
def test_gem_should_insert_on_separate_lines
@@ -66,8 +66,8 @@ class ActionsTest < Rails::Generators::TestCase
action :gem, 'rspec'
action :gem, 'rspec-rails'
- assert_file 'Gemfile', /^gem "rspec"$/
- assert_file 'Gemfile', /^gem "rspec-rails"$/
+ assert_file 'Gemfile', /^gem 'rspec'$/
+ assert_file 'Gemfile', /^gem 'rspec-rails'$/
end
def test_gem_should_include_options
@@ -75,7 +75,15 @@ class ActionsTest < Rails::Generators::TestCase
action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1'
- assert_file 'Gemfile', /gem "rspec", github: "dchelimsky\/rspec", tag: "1\.2\.9\.rc1"/
+ assert_file 'Gemfile', /gem 'rspec', github: 'dchelimsky\/rspec', tag: '1\.2\.9\.rc1'/
+ end
+
+ def test_gem_falls_back_to_inspect_if_string_contains_single_quote
+ run_generator
+
+ action :gem, 'rspec', ">=2.0'0"
+
+ assert_file 'Gemfile', /^gem 'rspec', ">=2\.0'0"$/
end
def test_gem_group_should_wrap_gems_in_a_group
@@ -89,7 +97,7 @@ class ActionsTest < Rails::Generators::TestCase
gem 'fakeweb'
end
- assert_file 'Gemfile', /\ngroup :development, :test do\n gem "rspec-rails"\nend\n\ngroup :test do\n gem "fakeweb"\nend/
+ assert_file 'Gemfile', /\ngroup :development, :test do\n gem 'rspec-rails'\nend\n\ngroup :test do\n gem 'fakeweb'\nend/
end
def test_environment_should_include_data_in_environment_initializer_block
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index eac28badfe..8d6dbf80c2 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -21,8 +21,10 @@ class GeneratorsTest < Rails::Generators::TestCase
end
def test_invoke_when_generator_is_not_found
- output = capture(:stdout){ Rails::Generators.invoke :unknown }
- assert_equal "Could not find generator unknown.\n", output
+ name = :unknown
+ output = capture(:stdout){ Rails::Generators.invoke name }
+ assert_match "Could not find generator '#{name}'", output
+ assert_match "scaffold", output
end
def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments