Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of git://github.com/rails/rails

  • Loading branch information...
commit 5e58fd889b4ba89ee2ac33b367a7366173e110b1 2 parents d22a58f + fef110d
Isaac Sanders authored
Showing with 698 additions and 157 deletions.
  1. +5 −1 actionpack/CHANGELOG.md
  2. +3 −0  actionpack/lib/action_controller/metal/exceptions.rb
  3. +1 −2  actionpack/lib/action_controller/metal/mime_responds.rb
  4. +3 −2 actionpack/lib/action_controller/metal/params_wrapper.rb
  5. +21 −6 actionpack/lib/action_controller/test_case.rb
  6. +7 −6 actionpack/lib/action_dispatch/middleware/cookies.rb
  7. +1 −0  actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
  8. +1 −1  actionpack/lib/action_dispatch/middleware/flash.rb
  9. +2 −2 actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
  10. +1 −1  actionpack/lib/action_dispatch/request/session.rb
  11. +2 −1  actionpack/lib/action_dispatch/routing/route_set.rb
  12. +3 −3 actionpack/lib/action_view/helpers/date_helper.rb
  13. +1 −1  actionpack/lib/action_view/helpers/form_tag_helper.rb
  14. +1 −1  actionpack/lib/action_view/helpers/tags/collection_helpers.rb
  15. +37 −0 actionpack/test/controller/action_pack_assertions_test.rb
  16. +9 −0 actionpack/test/controller/integration_test.rb
  17. +19 −13 actionpack/test/controller/mime_responds_test.rb
  18. +15 −2 actionpack/test/controller/params_wrapper_test.rb
  19. +4 −4 actionpack/test/controller/render_test.rb
  20. +24 −0 actionpack/test/dispatch/routing_test.rb
  21. +2 −0  actionpack/test/fixtures/test/hello_world_with_partial.html.erb
  22. +9 −0 actionpack/test/template/form_collections_helper_test.rb
  23. +6 −0 activerecord/CHANGELOG.md
  24. +3 −3 activerecord/lib/active_record/connection_adapters/column.rb
  25. +8 −0 activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
  26. +8 −0 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  27. +7 −2 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
  28. +2 −1  activerecord/lib/active_record/core.rb
  29. +1 −1  activerecord/lib/active_record/fixtures.rb
  30. +4 −4 activerecord/lib/active_record/relation/finder_methods.rb
  31. +13 −0 activerecord/test/cases/adapters/mysql/connection_test.rb
  32. +16 −0 activerecord/test/cases/adapters/mysql2/connection_test.rb
  33. +12 −0 activerecord/test/cases/adapters/postgresql/quoting_test.rb
  34. +7 −1 activerecord/test/cases/base_test.rb
  35. +28 −0 activerecord/test/cases/column_test.rb
  36. +4 −0 activerecord/test/cases/finder_test.rb
  37. +9 −1 activerecord/test/cases/relations_test.rb
  38. +4 −1 activerecord/test/schema/schema.rb
  39. +2 −0  activesupport/CHANGELOG.md
  40. +11 −0 activesupport/lib/active_support/core_ext/date_time/calculations.rb
  41. +0 −1  activesupport/lib/active_support/core_ext/hash.rb
  42. +0 −10 activesupport/lib/active_support/core_ext/hash/deep_dup.rb
  43. +1 −0  activesupport/lib/active_support/core_ext/object.rb
  44. +22 −0 activesupport/lib/active_support/core_ext/object/deep_dup.rb
  45. +15 −0 activesupport/lib/active_support/core_ext/time/calculations.rb
  46. +2 −2 activesupport/lib/active_support/multibyte/chars.rb
  47. +1 −1  activesupport/lib/active_support/ordered_options.rb
  48. +3 −3 activesupport/lib/active_support/time_with_zone.rb
  49. +8 −0 activesupport/test/core_ext/date_time_ext_test.rb
  50. +53 −0 activesupport/test/core_ext/deep_dup_test.rb
  51. +0 −15 activesupport/test/core_ext/hash_ext_test.rb
  52. +8 −0 activesupport/test/core_ext/time_ext_test.rb
  53. +23 −8 activesupport/test/core_ext/time_with_zone_test.rb
  54. +9 −0 activesupport/test/multibyte_chars_test.rb
  55. +8 −0 activesupport/test/ordered_options_test.rb
  56. +6 −0 guides/rails_guides/textile_extensions.rb
  57. +50 −14 guides/source/active_record_querying.textile
  58. +75 −0 guides/source/active_support_core_extensions.textile
  59. +1 −1  guides/source/index.html.erb
  60. +1 −2  railties/lib/rails/commands.rb
  61. +28 −10 railties/lib/rails/commands/dbconsole.rb
  62. +1 −1  railties/lib/rails/configuration.rb
  63. +4 −0 railties/lib/rails/engine/commands.rb
  64. +2 −0  railties/test/abstract_unit.rb
  65. +61 −29 railties/test/commands/dbconsole_test.rb
View
6 actionpack/CHANGELOG.md
@@ -1,5 +1,9 @@
## Rails 4.0.0 (unreleased) ##
+* `respond_to` and `respond_with` now raise ActionController::UnknownFormat instead
+ of directly returning head 406. The exception is rescued and converted to 406
+ in the exception handling middleware. *Steven Soroka*
+
* Allows `assert_redirected_to` to match against a regular expression. *Andy Lindeman*
* Add backtrace to development routing error page. *Richard Schneeman*
@@ -12,7 +16,7 @@
* Make current object and counter (when it applies) variables accessible when
rendering templates with :object / :collection. *Carlos Antonio da Silva*
-* JSONP now uses mimetype application/javascript instead of application/json *omjokine*
+* JSONP now uses mimetype application/javascript instead of application/json. *omjokine*
* Allow to lazy load `default_form_builder` by passing a `String` instead of a constant. *Piotr Sarnacki*
View
3  actionpack/lib/action_controller/metal/exceptions.rb
@@ -38,4 +38,7 @@ def initialize(message = nil)
class UnknownHttpMethod < ActionControllerError #:nodoc:
end
+
+ class UnknownFormat < ActionControllerError #:nodoc:
+ end
end
View
3  actionpack/lib/action_controller/metal/mime_responds.rb
@@ -375,8 +375,7 @@ def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
lookup_context.rendered_format = lookup_context.formats.first
collector
else
- head :not_acceptable
- nil
+ raise ActionController::UnknownFormat
end
end
View
5 actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -166,8 +166,9 @@ def _set_wrapper_defaults(options, model=nil)
unless options[:include] || options[:exclude]
model ||= _default_wrap_model
- if model.respond_to?(:accessible_attributes) && model.accessible_attributes.present?
- options[:include] = model.accessible_attributes.to_a
+ role = options.fetch(:as, :default)
+ if model.respond_to?(:accessible_attributes) && model.accessible_attributes(role).present?
+ options[:include] = model.accessible_attributes(role).to_a
elsif model.respond_to?(:attribute_names) && model.attribute_names.present?
options[:include] = model.attribute_names
end
View
27 actionpack/lib/action_controller/test_case.rb
@@ -20,7 +20,12 @@ def setup_subscriptions
ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
path = payload[:layout]
- @layouts[path] += 1
+ if path
+ @layouts[path] += 1
+ if path =~ /^layouts\/(.*)/
+ @layouts[$1] += 1
+ end
+ end
end
ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
@@ -59,6 +64,15 @@ def process(*args)
# # assert that the exact template "admin/posts/new" was rendered
# assert_template %r{\Aadmin/posts/new\Z}
#
+ # # assert that the layout 'admin' was rendered
+ # assert_template :layout => 'admin'
+ # assert_template :layout => 'layouts/admin'
+ # assert_template :layout => :admin
+ #
+ # # assert that no layout was rendered
+ # assert_template :layout => nil
+ # assert_template :layout => false
+ #
# # assert that the "_customer" partial was rendered twice
# assert_template :partial => '_customer', :count => 2
#
@@ -90,16 +104,17 @@ def assert_template(options = {}, message = nil)
end
end
when Hash
- if expected_layout = options[:layout]
+ if options.key?(:layout)
+ expected_layout = options[:layout]
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
expected_layout, @layouts.keys)
case expected_layout
- when String
- assert_includes @layouts.keys, expected_layout, msg
+ when String, Symbol
+ assert_includes @layouts.keys, expected_layout.to_s, msg
when Regexp
assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg)
- when nil
+ when nil, false
assert(@layouts.empty?, msg)
end
end
@@ -120,7 +135,7 @@ def assert_template(options = {}, message = nil)
options[:partial], @partials.keys)
assert_includes @partials, expected_partial, msg
end
- else
+ elsif options.key?(:partial)
assert @partials.empty?,
"Expected no partials to be rendered"
end
View
13 actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -2,7 +2,7 @@
require 'active_support/core_ext/hash/keys'
module ActionDispatch
- class Request
+ class Request < Rack::Request
def cookie_jar
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
end
@@ -26,9 +26,9 @@ def cookie_jar
# # Sets a cookie that expires in 1 hour.
# cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
#
- # # Sets a signed cookie, which prevents a user from tampering with its value.
+ # # Sets a signed cookie, which prevents users from tampering with its value.
# # The cookie is signed by your app's <tt>config.secret_token</tt> value.
- # # Rails generates this value by default when you create a new Rails app.
+ # # It can be read using the signed method <tt>cookies.signed[:key]</tt>
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -39,9 +39,10 @@ def cookie_jar
#
# Examples for reading:
#
- # cookies[:user_name] # => "david"
- # cookies.size # => 2
- # cookies[:lat_lon] # => [47.68, -122.37]
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ # cookies[:lat_lon] # => [47.68, -122.37]
+ # cookies.signed[:login] # => "XJ-122"
#
# Example for deleting:
#
View
1  actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -10,6 +10,7 @@ class ExceptionWrapper
'AbstractController::ActionNotFound' => :not_found,
'ActionController::MethodNotAllowed' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::UnknownFormat' => :not_acceptable,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
)
View
2  actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,5 +1,5 @@
module ActionDispatch
- class Request
+ class Request < Rack::Request
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
View
4 actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -12,8 +12,8 @@
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
- def debug_hash(hash)
- hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
end unless self.class.method_defined?(:debug_hash)
%>
View
2  actionpack/lib/action_dispatch/request/session.rb
@@ -37,7 +37,7 @@ def self.find(env)
def initialize(by, env, default_options)
@by = by
@env = env
- @delegate = default_options
+ @delegate = default_options.dup
end
def [](key)
View
3  actionpack/lib/action_dispatch/routing/route_set.rb
@@ -188,7 +188,8 @@ def define_url_helper(route, name, options)
remove_possible_method :#{selector}
def #{selector}(*args)
if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
- options = #{options.inspect}.merge!(url_options)
+ options = #{options.inspect}
+ options.merge!(url_options) if respond_to?(:url_options)
options[:path] = "#{optimized_helper(route)}"
ActionDispatch::Http::URL.url_for(options)
else
View
6 actionpack/lib/action_view/helpers/date_helper.rb
@@ -66,12 +66,12 @@ module DateHelper
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
- unless include_seconds_or_options.is_a?(Hash)
+ if include_seconds_or_options.is_a?(Hash)
+ options = include_seconds_or_options
+ else
ActiveSupport::Deprecation.warn "distance_of_time_in_words and time_ago_in_words now accept :include_seconds " +
"as a part of options hash, not a boolean argument", caller
options[:include_seconds] ||= !!include_seconds_or_options
- else
- options = include_seconds_or_options
end
from_time = from_time.to_time if from_time.respond_to?(:to_time)
View
2  actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -408,7 +408,7 @@ def radio_button_tag(name, value, checked = false, options = {})
# # => <input class="form_submit" name="commit" type="submit" />
#
# submit_tag "Edit", :disable_with => "Editing...", :class => "edit_button"
- # # => <input class="edit_button" data-disable_with="Editing..." name="commit" type="submit" value="Edit" />
+ # # => <input class="edit_button" data-disable-with="Editing..." name="commit" type="submit" value="Edit" />
#
# submit_tag "Save", :confirm => "Are you sure?"
# # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />
View
2  actionpack/lib/action_view/helpers/tags/collection_helpers.rb
@@ -49,7 +49,7 @@ def default_html_options_for_collection(item, value) #:nodoc:
accept = if current_value.respond_to?(:call)
current_value.call(item)
else
- Array(current_value).include?(value)
+ Array(current_value).map(&:to_s).include?(value.to_s)
end
if accept
View
37 actionpack/test/controller/action_pack_assertions_test.rb
@@ -76,6 +76,11 @@ def render_with_layout
render "test/hello_world", :layout => "layouts/standard"
end
+ def render_with_layout_and_partial
+ @variable_for_layout = nil
+ render "test/hello_world_with_partial", :layout => "layouts/standard"
+ end
+
def session_stuffing
session['xmas'] = 'turkey'
render :text => "ho ho ho"
@@ -473,11 +478,43 @@ def test_fails_with_wrong_layout
end
end
+ def test_fails_expecting_no_layout
+ get :render_with_layout
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :layout => nil
+ end
+ end
+
def test_passes_with_correct_layout
get :render_with_layout
assert_template :layout => "layouts/standard"
end
+ def test_passes_with_layout_and_partial
+ get :render_with_layout_and_partial
+ assert_template :layout => "layouts/standard"
+ end
+
+ def test_passed_with_no_layout
+ get :hello_world
+ assert_template :layout => nil
+ end
+
+ def test_passed_with_no_layout_false
+ get :hello_world
+ assert_template :layout => false
+ end
+
+ def test_passes_with_correct_layout_without_layouts_prefix
+ get :render_with_layout
+ assert_template :layout => "standard"
+ end
+
+ def test_passes_with_correct_layout_symbol
+ get :render_with_layout
+ assert_template :layout => :standard
+ end
+
def test_assert_template_reset_between_requests
get :hello_world
assert_template 'test/hello_world'
View
9 actionpack/test/controller/integration_test.rb
@@ -405,6 +405,15 @@ def test_xml_http_request_get
end
end
+ def test_request_with_bad_format
+ with_test_route_set do
+ xhr :get, '/get.php'
+ assert_equal 406, status
+ assert_response 406
+ assert_response :not_acceptable
+ end
+ end
+
def test_get_with_query_string
with_test_route_set do
get '/get_with_params?foo=bar'
View
32 actionpack/test/controller/mime_responds_test.rb
@@ -207,8 +207,9 @@ def test_html
get :html_or_xml
assert_equal 'HTML', @response.body
- get :just_xml
- assert_response 406
+ assert_raises(ActionController::UnknownFormat) do
+ get :just_xml
+ end
end
def test_all
@@ -239,8 +240,10 @@ def test_js_or_html
assert_equal 'HTML', @response.body
@request.accept = "text/javascript, text/html"
- xhr :get, :just_xml
- assert_response 406
+
+ assert_raises(ActionController::UnknownFormat) do
+ xhr :get, :just_xml
+ end
end
def test_json_or_yaml_with_leading_star_star
@@ -495,9 +498,9 @@ def test_format_with_custom_response_type_and_request_headers
end
def test_invalid_format
- get :using_defaults, :format => "invalidformat"
- assert_equal " ", @response.body
- assert_equal "text/html", @response.content_type
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_defaults, :format => "invalidformat"
+ end
end
end
@@ -701,12 +704,14 @@ def test_using_resource_with_overwrite_block
def test_not_acceptable
@request.accept = "application/xml"
- get :using_resource_with_block
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_resource_with_block
+ end
@request.accept = "text/javascript"
- get :using_resource_with_overwrite_block
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_resource_with_overwrite_block
+ end
end
def test_using_resource_for_post_with_html_redirects_on_success
@@ -984,8 +989,9 @@ def test_respond_as_responder_entry_point
def test_clear_respond_to
@controller = InheritedRespondWithController.new
@request.accept = "text/html"
- get :index
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :index
+ end
end
def test_first_in_respond_to_has_higher_priority
View
17 actionpack/test/controller/params_wrapper_test.rb
@@ -174,7 +174,7 @@ def test_derived_wrapped_keys_from_specified_model
def test_accessible_wrapped_keys_from_matching_model
User.expects(:respond_to?).with(:accessible_attributes).returns(true)
- User.expects(:accessible_attributes).twice.returns(["username"])
+ User.expects(:accessible_attributes).with(:default).twice.returns(["username"])
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
@@ -186,7 +186,7 @@ def test_accessible_wrapped_keys_from_matching_model
def test_accessible_wrapped_keys_from_specified_model
with_default_wrapper_options do
Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
- Person.expects(:accessible_attributes).twice.returns(["username"])
+ Person.expects(:accessible_attributes).with(:default).twice.returns(["username"])
UsersController.wrap_parameters Person
@@ -195,6 +195,19 @@ def test_accessible_wrapped_keys_from_specified_model
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
+
+ def test_accessible_wrapped_keys_with_role_from_specified_model
+ with_default_wrapper_options do
+ Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
+ Person.expects(:accessible_attributes).with(:admin).twice.returns(["username"])
+
+ UsersController.wrap_parameters Person, :as => :admin
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
def test_not_wrapping_abstract_model
User.expects(:respond_to?).with(:accessible_attributes).returns(false)
View
8 actionpack/test/controller/render_test.rb
@@ -102,12 +102,12 @@ def conditional_hello_with_expires_in_with_public_and_must_revalidate
end
def conditional_hello_with_expires_in_with_public_with_more_keys
- expires_in 1.minute, :public => true, 'max-stale' => 5.hours
+ expires_in 1.minute, :public => true, 's-maxage' => 5.hours
render :action => 'hello_world'
end
def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
- expires_in 1.minute, :public => true, :private => nil, 'max-stale' => 5.hours
+ expires_in 1.minute, :public => true, :private => nil, 's-maxage' => 5.hours
render :action => 'hello_world'
end
@@ -1478,12 +1478,12 @@ def test_expires_in_header_with_public_and_must_revalidate
def test_expires_in_header_with_additional_headers
get :conditional_hello_with_expires_in_with_public_with_more_keys
- assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
+ assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
end
def test_expires_in_old_syntax
get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
- assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
+ assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
end
def test_expires_now
View
24 actionpack/test/dispatch/routing_test.rb
@@ -2538,3 +2538,27 @@ def app; Routes end
assert_equal "bar", @request.params[:bar]
end
end
+
+class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+ get '/foo' => ok, as: :foo
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test 'enabled when not mounted and default_url_options is empty' do
+ assert Routes.url_helpers.optimize_routes_generation?
+ end
+
+ test 'named route called as singleton method' do
+ assert_equal '/foo', Routes.url_helpers.foo_path
+ end
+
+ test 'named route called on included module' do
+ assert_equal '/foo', foo_path
+ end
+end
View
2  actionpack/test/fixtures/test/hello_world_with_partial.html.erb
@@ -0,0 +1,2 @@
+Hello world!
+<%= render '/test/partial' %>
View
9 actionpack/test/template/form_collections_helper_test.rb
@@ -195,6 +195,15 @@ def with_collection_check_boxes(*args, &block)
assert_no_select 'input[type=checkbox][value=2][checked=checked]'
end
+ test 'collection check boxes accepts selected string values as :checked option' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => ['1', '3']
+
+ assert_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
test 'collection check boxes accepts a single checked value' do
collection = (1..3).map{|i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => 3
View
6 activerecord/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by
+ default to avoid silent data loss. This can be disabled by specifying
+ `strict: false` in your `database.yml`.
+
+ *Michael Pearson*
+
* Added default order to `first` to assure consistent results among
diferent database engines. Introduced `take` as a replacement to
the old behavior of `first`.
View
6 activerecord/lib/active_record/connection_adapters/column.rb
@@ -158,7 +158,7 @@ def binary_to_string(value)
def value_to_date(value)
if value.is_a?(String)
- return nil if value.empty?
+ return nil if value.blank?
fast_string_to_date(value) || fallback_string_to_date(value)
elsif value.respond_to?(:to_date)
value.to_date
@@ -169,14 +169,14 @@ def value_to_date(value)
def string_to_time(string)
return string unless string.is_a?(String)
- return nil if string.empty?
+ return nil if string.blank?
fast_string_to_time(string) || fallback_string_to_time(string)
end
def string_to_dummy_time(string)
return string unless string.is_a?(String)
- return nil if string.empty?
+ return nil if string.blank?
string_to_time "2000-01-01 #{string}"
end
View
8 activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -253,6 +253,14 @@ def configure_connection
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
variable_assignments = ['SQL_AUTO_IS_NULL=0']
+
+ # Make MySQL reject illegal values rather than truncating or
+ # blanking them. See
+ # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
+ if @config.fetch(:strict, true)
+ variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'"
+ end
+
encoding = @config[:encoding]
# make sure we set the encoding
View
8 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -53,6 +53,7 @@ module ConnectionAdapters
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html)
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -404,6 +405,13 @@ def configure_connection
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+
+ # Make MySQL reject illegal values rather than truncating or
+ # blanking them. See
+ # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
+ if @config.fetch(:strict, true)
+ execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging)
+ end
end
def select(sql, name = nil, binds = [])
View
9 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -511,8 +511,13 @@ def quote(value, column = nil) #:nodoc:
else super
end
when Float
- return super unless value.infinite? && column.type == :datetime
- "'#{value.to_s.downcase}'"
+ if value.infinite? && column.type == :datetime
+ "'#{value.to_s.downcase}'"
+ elsif value.infinite? || value.nan?
+ "'#{value.to_s}'"
+ else
+ super
+ end
when Numeric
return super unless column.sql_type == 'money'
# Not truly string input, so doesn't require (or allow) escape string syntax.
View
3  activerecord/lib/active_record/core.rb
@@ -1,5 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/object/deep_dup'
require 'thread'
module ActiveRecord
@@ -165,7 +166,7 @@ def relation #:nodoc:
# # Instantiates a single new object bypassing mass-assignment security
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
def initialize(attributes = nil, options = {})
- @attributes = self.class.initialize_attributes(self.class.column_defaults.dup)
+ @attributes = self.class.initialize_attributes(self.class.column_defaults.deep_dup)
@columns_hash = self.class.column_types.dup
init_internals
View
2  activerecord/lib/active_record/fixtures.rb
@@ -823,7 +823,7 @@ def run_in_transaction?
end
def setup_fixtures
- return unless !ActiveRecord::Base.configurations.blank?
+ return if ActiveRecord::Base.configurations.blank?
if pre_loaded_fixtures && !use_transactional_fixtures
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
View
8 activerecord/lib/active_record/relation/finder_methods.rb
@@ -51,13 +51,13 @@ def find(*args)
# Post.find_by "published_at < ?", 2.weeks.ago
#
def find_by(*args)
- where(*args).first
+ where(*args).take
end
# Like <tt>find_by</tt>, except that if no record is found, raises
# an <tt>ActiveRecord::RecordNotFound</tt> error.
def find_by!(*args)
- where(*args).first!
+ where(*args).take!
end
# Gives a record (or N records if a parameter is supplied) without any implied
@@ -269,7 +269,7 @@ def find_one(id)
substitute = connection.substitute_at(column, bind_values.length)
relation = where(table[primary_key].eq(substitute))
relation.bind_values += [[column, id]]
- record = relation.first
+ record = relation.take
unless record
conditions = arel.where_sql
@@ -309,7 +309,7 @@ def find_some(ids)
def find_take
if loaded?
- @records.take(1).first
+ @records.first
else
@take ||= limit(1).to_a.first
end
View
13 activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -120,6 +120,19 @@ def test_multi_results
end
end
+ def test_mysql_default_in_strict_mode
+ result = @connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ end
+
+ def test_mysql_strict_mode_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
+ result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
+ end
+ end
+
private
def run_without_connection
View
16 activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -29,6 +29,22 @@ def test_successful_reconnection_after_timeout_with_verify
assert @connection.active?
end
+ # TODO: Below is a straight up copy/paste from mysql/connection_test.rb
+ # I'm not sure what the correct way is to share these tests between
+ # adapters in minitest.
+ def test_mysql_default_in_strict_mode
+ result = @connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ end
+
+ def test_mysql_strict_mode_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
+ result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
+ end
+ end
+
private
def run_without_connection
View
12 activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -19,6 +19,18 @@ def test_type_cast_false
assert_equal 'f', @conn.type_cast(false, nil)
assert_equal 'f', @conn.type_cast(false, c)
end
+
+ def test_quote_float_nan
+ nan = 0.0/0
+ c = Column.new(nil, 1, 'float')
+ assert_equal "'NaN'", @conn.quote(nan, c)
+ end
+
+ def test_quote_float_infinity
+ infinity = 1.0/0
+ c = Column.new(nil, 1, 'float')
+ assert_equal "'Infinity'", @conn.quote(infinity, c)
+ end
end
end
end
View
8 activerecord/test/cases/base_test.rb
@@ -1909,7 +1909,7 @@ def test_marshalling_new_record_round_trip_with_associations
end
def test_attribute_names
- assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
+ assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"],
Company.attribute_names
end
@@ -2001,6 +2001,12 @@ def test_slice
assert_nil hash['firm_name']
end
+ def test_default_values_are_deeply_dupped
+ company = Company.new
+ company.description << "foo"
+ assert_equal "", Company.new.description
+ end
+
["find_by", "find_by!"].each do |meth|
test "#{meth} delegates to scoped" do
record = stub
View
28 activerecord/test/cases/column_test.rb
@@ -48,6 +48,34 @@ def test_type_cast_non_integer_to_integer
column.type_cast(false)
end
end
+
+ def test_type_cast_time
+ column = Column.new("field", nil, "time")
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ time_string = Time.now.utc.strftime("%T")
+ assert_equal time_string, column.type_cast(time_string).strftime("%T")
+ end
+
+ def test_type_cast_datetime_and_timestamp
+ [Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column|
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ datetime_string = Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
+ end
+ end
+
+ def test_type_cast_date
+ column = Column.new("field", nil, "date")
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ date_string = Time.now.utc.strftime("%F")
+ assert_equal date_string, column.type_cast(date_string).strftime("%F")
+ end
end
end
end
View
4 activerecord/test/cases/finder_test.rb
@@ -113,6 +113,10 @@ def test_find_an_empty_array
assert_equal [], Topic.find([])
end
+ def test_find_doesnt_have_implicit_ordering
+ assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
+ end
+
def test_find_by_ids_missing_one
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
end
View
10 activerecord/test/cases/relations_test.rb
@@ -255,7 +255,7 @@ def test_null_relation_calculations_methods
assert_equal nil, Developer.none.calculate(:average, 'salary')
end
end
-
+
def test_null_relation_metadata_methods
assert_equal "", Developer.none.to_sql
assert_equal({}, Developer.none.where_values_hash)
@@ -1294,6 +1294,10 @@ def test_presence
assert_equal nil, Post.scoped.find_by("1 = 0")
end
+ test "find_by doesn't have implicit ordering" do
+ assert_sql(/^((?!ORDER).)*$/) { Post.find_by(author_id: 2) }
+ end
+
test "find_by! with hash conditions returns the first matching record" do
assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2)
end
@@ -1306,6 +1310,10 @@ def test_presence
assert_equal posts(:eager_other), Post.order(:id).find_by!('author_id = ?', 2)
end
+ test "find_by! doesn't have implicit ordering" do
+ assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(author_id: 2) }
+ end
+
test "find_by! raises RecordNotFound if the record is missing" do
assert_raises(ActiveRecord::RecordNotFound) do
Post.scoped.find_by!("1 = 0")
View
5 activerecord/test/schema/schema.rb
@@ -38,7 +38,9 @@ def create_table(*args, &block)
create_table :admin_users, :force => true do |t|
t.string :name
t.text :settings, :null => true
- t.text :preferences, :null => false, :default => ""
+ # MySQL does not allow default values for blobs. Fake it out with a
+ # big varchar below.
+ t.string :preferences, :null => false, :default => '', :limit => 1024
t.references :account
end
@@ -173,6 +175,7 @@ def create_table(*args, &block)
t.integer :client_of
t.integer :rating, :default => 1
t.integer :account_id
+ t.string :description, :null => false, :default => ""
end
add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index"
View
2  activesupport/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* `deep_dup` works more expectedly now and duplicates also values in +Hash+ instances and elements in +Array+ instances. *Alexey Gaziev*
+
* Inflector no longer applies ice -> ouse to words like slice, police, ets *Wes Morgan*
* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations *twinturbo*
View
11 activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -91,6 +91,17 @@ def end_of_day
change(:hour => 23, :min => 59, :sec => 59)
end
+ # Returns a new DateTime representing the start of the hour (hh:00:00)
+ def beginning_of_hour
+ change(:min => 0)
+ end
+ alias :at_beginning_of_hour :beginning_of_hour
+
+ # Returns a new DateTime representing the end of the hour (hh:59:59)
+ def end_of_hour
+ change(:min => 59, :sec => 59)
+ end
+
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0
#
# Example:
View
1  activesupport/lib/active_support/core_ext/hash.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/deep_merge'
-require 'active_support/core_ext/hash/deep_dup'
require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/indifferent_access'
View
10 activesupport/lib/active_support/core_ext/hash/deep_dup.rb
@@ -1,10 +0,0 @@
-class Hash
- # Returns a deep copy of hash.
- def deep_dup
- duplicate = self.dup
- duplicate.each_pair do |k,v|
- duplicate[k] = v.is_a?(Hash) ? v.deep_dup : v
- end
- duplicate
- end
-end
View
1  activesupport/lib/active_support/core_ext/object.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/object/deep_dup'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
View
22 activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -0,0 +1,22 @@
+class Object
+ # Returns a deep copy of object if it's duplicable.
+ def deep_dup
+ duplicable? ? dup : self
+ end
+end
+
+class Array
+ # Returns a deep copy of array.
+ def deep_dup
+ map { |it| it.deep_dup }
+ end
+end
+
+class Hash
+ # Returns a deep copy of hash.
+ def deep_dup
+ each_with_object(dup) do |(key, value), hash|
+ hash[key.deep_dup] = value.deep_dup
+ end
+ end
+end
View
15 activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -257,6 +257,21 @@ def end_of_day
)
end
+ # Returns a new Time representing the start of the hour (x:00)
+ def beginning_of_hour
+ change(:min => 0)
+ end
+ alias :at_beginning_of_hour :beginning_of_hour
+
+ # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
+ def end_of_hour
+ change(
+ :min => 59,
+ :sec => 59,
+ :usec => 999999.999
+ )
+ end
+
# Returns a new Time representing the start of the month (1st of the month, 0:00)
def beginning_of_month
#self - ((self.mday-1).days + self.seconds_since_midnight)
View
4 activesupport/lib/active_support/multibyte/chars.rb
@@ -62,8 +62,8 @@ def method_missing(method, *args, &block)
# Returns +true+ if _obj_ responds to the given method. Private methods are included in the search
# only if the optional second parameter evaluates to +true+.
- def respond_to?(method, include_private=false)
- super || @wrapped_string.respond_to?(method, include_private)
+ def respond_to_missing?(method, include_private)
+ @wrapped_string.respond_to?(method, include_private)
end
# Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
View
2  activesupport/lib/active_support/ordered_options.rb
@@ -36,7 +36,7 @@ def method_missing(name, *args)
end
end
- def respond_to?(name)
+ def respond_to_missing?(name, include_private)
true
end
end
View
6 activesupport/lib/active_support/time_with_zone.rb
@@ -311,10 +311,10 @@ def marshal_load(variables)
end
# Ensure proxy class responds to all methods that underlying time instance responds to.
- def respond_to?(sym, include_priv = false)
+ def respond_to_missing?(sym, include_priv)
# consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
- return false if sym.to_s == 'acts_like_date?'
- super || time.respond_to?(sym, include_priv)
+ return false if sym.to_sym == :acts_like_date?
+ time.respond_to?(sym, include_priv)
end
# Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+.
View
8 activesupport/test/core_ext/date_time_ext_test.rb
@@ -91,6 +91,14 @@ def test_end_of_day
assert_equal DateTime.civil(2005,2,4,23,59,59), DateTime.civil(2005,2,4,10,10,10).end_of_day
end
+ def test_beginning_of_hour
+ assert_equal DateTime.civil(2005,2,4,19,0,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_hour
+ end
+
+ def test_end_of_hour
+ assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour
+ end
+
def test_beginning_of_month
assert_equal DateTime.civil(2005,2,1,0,0,0), DateTime.civil(2005,2,22,10,10,10).beginning_of_month
end
View
53 activesupport/test/core_ext/deep_dup_test.rb
@@ -0,0 +1,53 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class DeepDupTest < ActiveSupport::TestCase
+
+ def test_array_deep_dup
+ array = [1, [2, 3]]
+ dup = array.deep_dup
+ dup[1][2] = 4
+ assert_equal nil, array[1][2]
+ assert_equal 4, dup[1][2]
+ end
+
+ def test_hash_deep_dup
+ hash = { :a => { :b => 'b' } }
+ dup = hash.deep_dup
+ dup[:a][:c] = 'c'
+ assert_equal nil, hash[:a][:c]
+ assert_equal 'c', dup[:a][:c]
+ end
+
+ def test_array_deep_dup_with_hash_inside
+ array = [1, { :a => 2, :b => 3 } ]
+ dup = array.deep_dup
+ dup[1][:c] = 4
+ assert_equal nil, array[1][:c]
+ assert_equal 4, dup[1][:c]
+ end
+
+ def test_hash_deep_dup_with_array_inside
+ hash = { :a => [1, 2] }
+ dup = hash.deep_dup
+ dup[:a][2] = 'c'
+ assert_equal nil, hash[:a][2]
+ assert_equal 'c', dup[:a][2]
+ end
+
+ def test_deep_dup_initialize
+ zero_hash = Hash.new 0
+ hash = { :a => zero_hash }
+ dup = hash.deep_dup
+ assert_equal 0, dup[:a][44]
+ end
+
+ def test_object_deep_dup
+ object = Object.new
+ dup = object.deep_dup
+ dup.instance_variable_set(:@a, 1)
+ assert !object.instance_variable_defined?(:@a)
+ assert dup.instance_variable_defined?(:@a)
+ end
+
+end
View
15 activesupport/test/core_ext/hash_ext_test.rb
@@ -363,21 +363,6 @@ def test_deep_merge_on_indifferent_access
assert_equal expected, hash_1
end
- def test_deep_dup
- hash = { :a => { :b => 'b' } }
- dup = hash.deep_dup
- dup[:a][:c] = 'c'
- assert_equal nil, hash[:a][:c]
- assert_equal 'c', dup[:a][:c]
- end
-
- def test_deep_dup_initialize
- zero_hash = Hash.new 0
- hash = { :a => zero_hash }
- dup = hash.deep_dup
- assert_equal 0, dup[:a][44]
- end
-
def test_store_on_indifferent_access
hash = HashWithIndifferentAccess.new
hash.store(:test1, 1)
View
8 activesupport/test/core_ext/time_ext_test.rb
@@ -93,6 +93,10 @@ def test_beginning_of_day
end
end
+ def test_beginning_of_hour
+ assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour
+ end
+
def test_beginning_of_month
assert_equal Time.local(2005,2,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_month
end
@@ -127,6 +131,10 @@ def test_end_of_week
assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday
end
+ def test_end_of_hour
+ assert_equal Time.local(2005,2,4,19,59,59,999999.999), Time.local(2005,2,4,19,30,10).end_of_hour
+ end
+
def test_end_of_month
assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month
assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month
View
31 activesupport/test/core_ext/time_with_zone_test.rb
@@ -191,7 +191,7 @@ def test_future_with_time_current_as_time_local
end
end
- def future_with_time_current_as_time_with_zone
+ def test_future_with_time_current_as_time_with_zone
twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
Time.stubs(:current).returns(twz)
assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
@@ -450,6 +450,7 @@ def test_instance_created_with_local_time_enforces_fall_dst_rules
def test_ruby_19_weekday_name_query_methods
%w(sunday? monday? tuesday? wednesday? thursday? friday? saturday?).each do |name|
assert_respond_to @twz, name
+ assert_equal @twz.send(name), @twz.method(name).call
end
end
@@ -482,36 +483,50 @@ def test_advance
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(:seconds => 30).inspect
end
- def beginning_of_year
+ def test_beginning_of_year
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 01 Jan 1999 00:00:00 EST -05:00", @twz.beginning_of_year.inspect
end
- def end_of_year
+ def test_end_of_year
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_year.inspect
end
- def beginning_of_month
+ def test_beginning_of_month
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
- assert_equal "Fri, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect
+ assert_equal "Wed, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect
end
- def end_of_month
+ def test_end_of_month
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_month.inspect
end
- def beginning_of_day
+ def test_beginning_of_day
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_day.inspect
end
- def end_of_day
+ def test_end_of_day
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_day.inspect
end
+ def test_beginning_of_hour
+ utc = Time.utc(2000, 1, 1, 0, 30)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect
+ end
+
+ def test_end_of_hour
+ utc = Time.utc(2000, 1, 1, 0, 30)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect
+ end
+
def test_since
assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect
end
View
9 activesupport/test/multibyte_chars_test.rb
@@ -458,6 +458,15 @@ def test_respond_to_knows_which_methods_the_proxy_responds_to
assert !''.mb_chars.respond_to?(:undefined_method) # Not defined
end
+ def test_method_works_for_proxyed_methods
+ assert_equal 'll', 'hello'.mb_chars.method(:slice).call(2..3) # Defined on Chars
+ chars = 'hello'.mb_chars
+ assert_equal 'Hello', chars.method(:capitalize!).call # Defined on Chars
+ assert_equal 'Hello', chars
+ assert_equal 'jello', 'hello'.mb_chars.method(:gsub).call(/h/, 'j') # Defined on String
+ assert_raise(NameError){ ''.mb_chars.method(:undefined_method) } # Not defined
+ end
+
def test_acts_like_string
assert 'Bambi'.mb_chars.acts_like_string?
end
View
8 activesupport/test/ordered_options_test.rb
@@ -77,4 +77,12 @@ def test_inheritable_options_inheritable_copy
assert copy.kind_of?(original.class)
assert_not_equal copy.object_id, original.object_id
end
+
+ def test_introspection
+ a = ActiveSupport::OrderedOptions.new
+ assert a.respond_to?(:blah)
+ assert a.respond_to?(:blah=)
+ assert_equal 42, a.method(:blah=).call(42)
+ assert_equal 42, a.method(:blah).call
+ end
end
View
6 guides/rails_guides/textile_extensions.rb
@@ -1,5 +1,11 @@
require 'active_support/core_ext/object/inclusion'
+module RedCloth::Formatters::HTML
+ def emdash(opts)
+ "--"
+ end
+end
+
module RailsGuides
module TextileExtensions
def notestuff(body)
View
64 guides/source/active_record_querying.textile
@@ -99,9 +99,28 @@ SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
<tt>Model.find(primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception if no matching record is found.
+h5. +take+
+
+<tt>Model.take</tt> retrieves a record without any implicit ordering. For example:
+
+<ruby>
+client = Client.take
+# => #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.take</tt> returns +nil+ if no record is found and no exception will be raised.
+
+TIP: The retrieved record may vary depending on the database engine.
+
h5. +first+
-<tt>Model.first</tt> finds the first record matched by the supplied options, if any. For example:
+<tt>Model.first</tt> finds the first record ordered by the primary key. For example:
<ruby>
client = Client.first
@@ -111,14 +130,14 @@ client = Client.first
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</sql>
-<tt>Model.first</tt> returns +nil+ if no matching record is found. No exception will be raised.
+<tt>Model.first</tt> returns +nil+ if no matching record is found and no exception will be raised.
h5. +last+
-<tt>Model.last</tt> finds the last record matched by the supplied options. For example:
+<tt>Model.last</tt> finds the last record ordered by the primary key. For example:
<ruby>
client = Client.last
@@ -131,7 +150,7 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</sql>
-<tt>Model.last</tt> returns +nil+ if no matching record is found. No exception will be raised.
+<tt>Model.last</tt> returns +nil+ if no matching record is found and no exception will be raised.
h5. +find_by+
@@ -148,12 +167,29 @@ Client.find_by first_name: 'Jon'
It is equivalent to writing:
<ruby>
-Client.where(first_name: 'Lifo').first
+Client.where(first_name: 'Lifo').take
</ruby>
+h5(#take_1). +take!+
+
+<tt>Model.take!</tt> retrieves a record without any implicit ordering. For example:
+
+<ruby>
+client = Client.take!
+# => #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.take!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
+
h5(#first_1). +first!+
-<tt>Model.first!</tt> finds the first record. For example:
+<tt>Model.first!</tt> finds the first record ordered by the primary key. For example:
<ruby>
client = Client.first!
@@ -163,14 +199,14 @@ client = Client.first!
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</sql>
-<tt>Model.first!</tt> raises +RecordNotFound+ if no matching record is found.
+<tt>Model.first!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
h5(#last_1). +last!+
-<tt>Model.last!</tt> finds the last record. For example:
+<tt>Model.last!</tt> finds the last record ordered by the primary key. For example:
<ruby>
client = Client.last!
@@ -183,24 +219,24 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</sql>
-<tt>Model.last!</tt> raises +RecordNotFound+ if no matching record is found.
+<tt>Model.last!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
h5(#find_by_1). +find_by!+
-<tt>Model.find_by!</tt> finds the first record matching some conditions. It raises +RecordNotFound+ if no matching record is found. For example:
+<tt>Model.find_by!</tt> finds the first record matching some conditions. It raises +ActiveRecord::RecordNotFound+ if no matching record is found. For example:
<ruby>
Client.find_by! first_name: 'Lifo'
# => #<Client id: 1, first_name: "Lifo">
Client.find_by! first_name: 'Jon'
-# => RecordNotFound
+# => ActiveRecord::RecordNotFound
</ruby>
It is equivalent to writing:
<ruby>
-Client.where(first_name: 'Lifo').first!
+Client.where(first_name: 'Lifo').take!
</ruby>
h4. Retrieving Multiple Objects
View
75 guides/source/active_support_core_extensions.textile
@@ -154,6 +154,51 @@ WARNING. Any class can disallow duplication removing +dup+ and +clone+ or raisin
NOTE: Defined in +active_support/core_ext/object/duplicable.rb+.
+h4. +deep_dup+
+
+The +deep_dup+ method returns deep copy of given object. Normally, when you +dup+ an object that contains other objects, ruby does not +dup+ them. If you have array with a string, for example, it will look like this:
+
+<ruby>
+array = ['string']
+duplicate = array.dup
+
+duplicate.push 'another-string'
+
+# object was duplicated, element added only to duplicate
+array #=> ['string']
+duplicate #=> ['string', 'another-string']
+
+duplicate.first.gsub!('string', 'foo')
+
+# first element was not duplicated, it will be changed for both arrays
+array #=> ['foo']
+duplicate #=> ['foo', 'another-string']
+</ruby>
+
+As you can see, after duplicating +Array+ instance, we got another object, therefore we can modify it and the original object will stay unchanged. This is not true for array's elements, however. Since +dup+ does not make deep copy, the string inside array is still the same object.
+
+If you need a deep copy of an object, you should use +deep_dup+ in such situation:
+
+<ruby>
+array = ['string']
+duplicate = array.deep_dup
+
+duplicate.first.gsub!('string', 'foo')
+
+array #=> ['string']
+duplicate #=> ['foo']
+</ruby>
+
+If object is not duplicable +deep_dup+ will just return this object:
+
+<ruby>
+number = 1
+dup = number.deep_dup
+number.object_id == dup.object_id # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/object/deep_dup.rb+.
+
h4. +try+
Sometimes you want to call a method provided the receiver object is not +nil+, which is something you usually check first. +try+ is like +Object#send+ except that it returns +nil+ if sent to +nil+.
@@ -2217,6 +2262,19 @@ Thus, in this case the behavior is different for +nil+, and the differences with
NOTE: Defined in +active_support/core_ext/array/wrap.rb+.
+h4. Duplicating
+
+The method +Array.deep_dup+ duplicates itself and all objects inside recursively with ActiveSupport method +Object#deep_dup+. It works like +Array#map+ with sending +deep_dup+ method to each object inside.
+
+<ruby>
+array = [1, [2, 3]]
+dup = array.deep_dup
+dup[1][2] = 4
+array[1][2] == nil # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/array/deep_dup.rb+.
+
h4. Grouping
h5. +in_groups_of(number, fill_with = nil)+
@@ -2423,6 +2481,23 @@ The method +deep_merge!+ performs a deep merge in place.
NOTE: Defined in +active_support/core_ext/hash/deep_merge.rb+.
+h4. Deep duplicating
+
+The method +Hash.deep_dup+ duplicates itself and all keys and values inside recursively with ActiveSupport method +Object#deep_dup+. It works like +Enumerator#each_with_object+ with sending +deep_dup+ method to each pair inside.
+
+<ruby>
+hash = { :a => 1, :b => { :c => 2, :d => [3, 4] } }
+
+dup = hash.deep_dup
+dup[:b][:e] = 5
+dup[:b][:d] << 5
+
+hash[:b][:e] == nil # => true
+hash[:b][:d] == [3, 4] # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/hash/deep_dup.rb+.
+
h4. Diffing
The method +diff+ returns a hash that represents a diff of the receiver and the argument with the following logic:
View
2  guides/source/index.html.erb
@@ -13,7 +13,7 @@ Ruby on Rails Guides
and <%= link_to 'Free Kindle Reading Apps', 'http://www.amazon.com/gp/kindle/kcp' %> for the iPad,
iPhone, Mac, Android, etc. Download them from <%= link_to 'here', @mobi %>.
</dd>
- <dd class="work-in-progress">Guides marked with this icon are currently being worked on. While they might still be useful to you, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections to the author.</dd>
+ <dd class="work-in-progress">Guides marked with this icon are currently being worked on and will not be available in the Guides Index menu. While still useful, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections.</dd>
</dl>
</div>
<% end %>
View
3  railties/lib/rails/commands.rb
@@ -57,8 +57,7 @@
when 'dbconsole'
require 'rails/commands/dbconsole'
- require APP_PATH
- Rails::DBConsole.start(Rails.application)
+ Rails::DBConsole.start
when 'application', 'runner'
require "rails/commands/#{command}"
View
38 railties/lib/rails/commands/dbconsole.rb
@@ -5,15 +5,37 @@
module Rails
class DBConsole
- attr_reader :arguments
+ attr_reader :arguments, :config
- def self.start(app)
- new(app).start
+ def self.start
+ new(config).start
end
- def initialize(app, arguments = ARGV)
- @app = app
- @arguments = arguments
+ def self.config
+ config = begin
+ YAML.load(ERB.new(IO.read("config/database.yml")).result)
+ rescue SyntaxError, StandardError
+ require APP_PATH
+ Rails.application.config.database_configuration
+ end
+
+ unless config[env]
+ abort "No database is configured for the environment '#{env}'"
+ end
+
+ config[env]
+ end
+
+ def self.env
+ if Rails.respond_to?(:env)
+ Rails.env
+ else
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
+ end
+ end
+
+ def initialize(config, arguments = ARGV)
+ @config, @arguments = config, arguments
end
def start
@@ -38,10 +60,6 @@ def start
abort opt.to_s unless (0..1).include?(arguments.size)
end
- unless config = @app.config.database_configuration[Rails.env]
- abort "No database is configured for the environment '#{Rails.env}'"
- end
-
case config["adapter"]
when /^mysql/
View
2  railties/lib/rails/configuration.rb
@@ -1,6 +1,6 @@
require 'active_support/deprecation'
require 'active_support/ordered_options'
-require 'active_support/core_ext/hash/deep_dup'
+require 'active_support/core_ext/object'
require 'rails/paths'
require 'rails/rack'
View
4 railties/lib/rails/engine/commands.rb
@@ -34,6 +34,10 @@
destroy Undo code generated with "generate" (short-cut alias: "d")
All commands can be run with -h for more information.
+
+If you want to run any commands that need to be run in context
+of the application, like `rails server` or `rails console`,
+you should do it from application's directory (typically test/dummy).
EOT
exit(1)
end
View
2  railties/test/abstract_unit.rb
@@ -1,3 +1,5 @@
+ENV["RAILS_ENV"] ||= "test"
+
require File.expand_path("../../../load_paths", __FILE__)
require 'stringio'
View
90 railties/test/commands/dbconsole_test.rb
@@ -3,42 +3,73 @@
class Rails::DBConsoleTest < ActiveSupport::TestCase
def teardown
- %w[PGUSER PGHOST PGPORT PGPASSWORD'].each{|key| ENV.delete(key)}
+ %w[PGUSER PGHOST PGPORT PGPASSWORD].each{|key| ENV.delete(key)}
end
- def test_no_database_configured
- start [], false
+ def test_config
+ Rails::DBConsole.const_set(:APP_PATH, "erb")
+
+ app_config({})
+ capture_abort { Rails::DBConsole.config }
assert aborted
assert_match /No database is configured for the environment '\w+'/, output
+
+ app_config(test: "with_init")
+ assert_equal Rails::DBConsole.config, "with_init"
+
+ app_db_file("test:\n without_init")
+ assert_equal Rails::DBConsole.config, "without_init"
+
+ app_db_file("test:\n <%= Rails.something_app_specific %>")
+ assert_equal Rails::DBConsole.config, "with_init"
+
+ app_db_file("test:\n\ninvalid")
+ assert_equal Rails::DBConsole.config, "with_init"
+ end
+
+ def test_env
+ assert_equal Rails::DBConsole.env, "test"
+
+ Rails.stubs(:respond_to?).with(:env).returns(false)
+ assert_equal Rails::DBConsole.env, "test"
+
+ ENV['RAILS_ENV'] = nil
+ ENV['RACK_ENV'] = "rack_env"
+ assert_equal Rails::DBConsole.env, "rack_env"
+
+ ENV['RAILS_ENV'] = "rails_env"
+ assert_equal Rails::DBConsole.env, "rails_env"
+ ensure
+ ENV['RAILS_ENV'] = "test"
end
def test_mysql
dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], 'db')
- start [], {adapter: 'mysql', database: 'db'}
+ start(adapter: 'mysql', database: 'db')
assert !aborted
end
def test_mysql_full
dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db')
- start [], {adapter: 'mysql', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8'}
+ start(adapter: 'mysql', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8')
assert !aborted
end
def test_mysql_include_password
dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--user=user', '--password=qwerty', 'db')
- start ['-p'], {adapter: 'mysql', database: 'db', username: 'user', password: 'qwerty'}
+ start({adapter: 'mysql', database: 'db', username: 'user', password: 'qwerty'}, ['-p'])
assert !aborted
end
def test_postgresql
dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
- start [], {adapter: 'postgresql', database: 'db'}
+ start(adapter: 'postgresql', database: 'db')
assert !aborted
end
def test_postgresql_full
dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
- start [], {adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3', host: 'host', port: 5432}
+ start(adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3', host: 'host', port: 5432)
assert !aborted
assert_equal 'user', ENV['PGUSER']
assert_equal 'host', ENV['PGHOST']
@@ -48,7 +79,7 @@ def test_postgresql_full
def test_postgresql_include_password
dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
- start ['-p'], {adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3'}
+ start({adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3'}, ['-p'])
assert !aborted
assert_equal 'user', ENV['PGUSER']
assert_equal 'q1w2e3', ENV['PGPASSWORD']
@@ -56,42 +87,42 @@ def test_postgresql_include_password
def test_sqlite
dbconsole.expects(:find_cmd_and_exec).with('sqlite', 'db')
- start [], {adapter: 'sqlite', database: 'db'}
+ start(adapter: 'sqlite', database: 'db')
assert !aborted
end
def test_sqlite3
dbconsole.expects(:find_cmd_and_exec).with('sqlite3', 'db')
- start [], {adapter: 'sqlite3', database: 'db'}
+ start(adapter: 'sqlite3', database: 'db')
assert !aborted
end
def test_sqlite3_mode
dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', 'db')
- start ['--mode', 'html'], {adapter: 'sqlite3', database: 'db'}
+ start({adapter: 'sqlite3', database: 'db'}, ['--mode', 'html'])
assert !aborted
end
def test_sqlite3_header
dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', 'db')
- start ['--header'], {adapter: 'sqlite3', database: 'db'}
+ start({adapter: 'sqlite3', database: 'db'}, ['--header'])
assert !aborted
end
def test_oracle
dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user@db')
- start [], {adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}
+ start(adapter: 'oracle', database: 'db', username: 'user', password: 'secret')
assert !aborted
end
def test_oracle_include_password
dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user/secret@db')
- start ['-p'], {adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}
+ start({adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}, ['-p'])
assert !aborted
end
def test_unknown_command_line_client
- start [], {adapter: 'unknown', database: 'db'}
+ start(adapter: 'unknown', database: 'db')
assert aborted
assert_match /Unknown command-line client for db/, output
end
@@ -100,29 +131,30 @@ def test_unknown_command_line_client
attr_reader :aborted, :output
def dbconsole
- @dbconsole ||= Rails::DBConsole.new(app)
+ @dbconsole ||= Rails::DBConsole.new(nil)
end
- def start(argv = [], database_configuration = {})
- dbconsole.stubs(arguments: argv)
- app.config.stubs(database_configuration: {
- Rails.env => database_configuration ? database_configuration.stringify_keys : database_configuration
- })
+ def start(config = {}, argv = [])
+ dbconsole.stubs(config: config.stringify_keys, arguments: argv)
+ capture_abort { dbconsole.start }
+ end
+ def capture_abort
@aborted = false
- @output = capture(:stderr) do
+ @output = capture(:stderr) do
begin
- dbconsole.start
+ yield
rescue SystemExit
@aborted = true
end
end
end
- def app
- @app ||= begin
- config = mock("config")
- stub("app", config: config)
- end
+ def app_db_file(result)
+ IO.stubs(:read).with("config/database.yml").returns(result)
+ end
+
+ def app_config(result)
+ Rails.application.config.stubs(:database_configuration).returns(result.stringify_keys)
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.