Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into nested_has_many_through

Conflicts:
	activerecord/lib/active_record/associations.rb
  • Loading branch information...
commit e05162cffad7ae86615c21c6b54ab161d0261c39 2 parents 083d6f2 + 4e0477c
@jonleighton jonleighton authored
Showing with 381 additions and 185 deletions.
  1. +5 −0 Gemfile
  2. +1 −1  actionpack/lib/action_controller/metal/mime_responds.rb
  3. +0 −9 actionpack/lib/action_controller/metal/testing.rb
  4. +3 −2 actionpack/lib/action_controller/test_case.rb
  5. +19 −2 actionpack/lib/action_dispatch/http/request.rb
  6. +3 −2 actionpack/lib/action_dispatch/routing/mapper.rb
  7. +1 −1  actionpack/lib/action_dispatch/testing/test_process.rb
  8. +2 −1  actionpack/lib/action_view/helpers/capture_helper.rb
  9. +1 −2  actionpack/lib/action_view/helpers/form_helper.rb
  10. +21 −10 actionpack/lib/action_view/helpers/number_helper.rb
  11. +6 −1 actionpack/lib/action_view/template/handlers/erb.rb
  12. +16 −0 actionpack/test/controller/mime_responds_test.rb
  13. +7 −0 actionpack/test/controller/new_base/bare_metal_test.rb
  14. +10 −0 actionpack/test/controller/new_base/render_template_test.rb
  15. +18 −0 actionpack/test/dispatch/cookies_test.rb
  16. +31 −0 actionpack/test/dispatch/routing_test.rb
  17. +10 −0 actionpack/test/template/capture_helper_test.rb
  18. +4 −1 actionpack/test/template/number_helper_i18n_test.rb
  19. +4 −0 actionpack/test/template/number_helper_test.rb
  20. +1 −6 actionpack/test/template/url_helper_test.rb
  21. +7 −0 activemodel/test/cases/serializeration/xml_serialization_test.rb
  22. +18 −20 activerecord/lib/active_record/associations.rb
  23. +1 −1  activerecord/lib/active_record/associations/association_proxy.rb
  24. +1 −1  activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
  25. +2 −2 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  26. +8 −8 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
  27. +11 −11 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
  28. +6 −4 activerecord/lib/active_record/relation/finder_methods.rb
  29. +1 −1  activerecord/lib/active_record/timestamp.rb
  30. +17 −25 activerecord/lib/active_record/validations/uniqueness.rb
  31. +13 −13 activerecord/test/cases/adapters/mysql/connection_test.rb
  32. +9 −9 activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
  33. +2 −2 activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
  34. +12 −12 activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
  35. +1 −1  activerecord/test/cases/associations/cascaded_eager_loading_test.rb
  36. +2 −2 activerecord/test/cases/associations/eager_test.rb
  37. +1 −1  activerecord/test/cases/base_test.rb
  38. +14 −0 activerecord/test/cases/dirty_test.rb
  39. +7 −3 activerecord/test/cases/helper.rb
  40. +5 −5 activerecord/test/cases/reflection_test.rb
  41. +12 −1 activerecord/test/cases/relations_test.rb
  42. +0 −12 activerecord/test/connections/native_oracle/connection.rb
  43. +1 −0  activerecord/test/schema/schema.rb
  44. +3 −4 activesupport/lib/active_support/cache/file_store.rb
  45. +4 −2 activesupport/lib/active_support/hash_with_indifferent_access.rb
  46. +2 −0  activesupport/lib/active_support/ordered_hash.rb
  47. +4 −2 activesupport/lib/active_support/xml_mini.rb
  48. +9 −0 activesupport/test/caching_test.rb
  49. +22 −2 activesupport/test/core_ext/hash_ext_test.rb
  50. +8 −0 activesupport/test/ordered_hash_test.rb
  51. +14 −2 activesupport/test/test_xml_mini.rb
  52. +1 −1  railties/railties.gemspec
View
5 Gemfile
@@ -31,6 +31,11 @@ platforms :mri_18 do
gem 'ruby-prof'
end
+platforms :mri_19 do
+ # TODO: Remove the conditional when ruby-debug19 supports Ruby >= 1.9.3
+ gem "ruby-debug19" if RUBY_VERSION < "1.9.3"
+end
+
platforms :ruby do
gem 'json'
gem 'yajl-ruby'
View
2  actionpack/lib/action_controller/metal/mime_responds.rb
@@ -227,7 +227,7 @@ def respond_with(*resources, &block)
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
if response = retrieve_response_from_mimes(&block)
- options = resources.extract_options!
+ options = resources.size == 1 ? {} : resources.extract_options!
options.merge!(:default_response => response)
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
View
9 actionpack/lib/action_controller/metal/testing.rb
@@ -14,18 +14,9 @@ def process_with_new_base_test(request, response)
cookies.write(@_response)
end
@_response.prepare!
- set_test_assigns
ret
end
- def set_test_assigns
- @assigns = {}
- (instance_variable_names - self.class.protected_instance_variables).each do |var|
- name, value = var[1..-1], instance_variable_get(var)
- @assigns[name] = value
- end
- end
-
# TODO : Rewrite tests using controller.headers= to use Rack env
def headers=(new_headers)
@_response ||= ActionDispatch::Response.new
View
5 actionpack/lib/action_controller/test_case.rb
@@ -411,8 +411,9 @@ def process(action, parameters = nil, session = nil, flash = nil, http_method =
@controller.request = @request
@controller.params.merge!(parameters)
build_request_uri(action, parameters)
- Base.class_eval { include Testing }
+ @controller.class.class_eval { include Testing }
@controller.process_with_new_base_test(@request, @response)
+ @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
@request.session.delete('flash') if @request.session['flash'].blank?
@response
end
@@ -448,7 +449,7 @@ def rescue_action_in_public!
def build_request_uri(action, parameters)
unless @request.env["PATH_INFO"]
- options = @controller.__send__(:url_options).merge(parameters)
+ options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
options.update(
:only_path => true,
:action => action,
View
21 actionpack/lib/action_dispatch/http/request.rb
@@ -4,6 +4,7 @@
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/string/access'
+require 'active_support/inflector'
require 'action_dispatch/http/headers'
module ActionDispatch
@@ -44,8 +45,24 @@ def key?(key)
@env.key?(key)
end
- HTTP_METHODS = %w(get head put post delete options)
- HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
+ # List of HTTP request methods from the following RFCs:
+ # Hypertext Transfer Protocol -- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt)
+ # HTTP Extensions for Distributed Authoring -- WEBDAV (http://www.ietf.org/rfc/rfc2518.txt)
+ # Versioning Extensions to WebDAV (http://www.ietf.org/rfc/rfc3253.txt)
+ # Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt)
+ # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt)
+ # Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt)
+ # PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt)
+ RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
+ RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
+ RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
+ RFC3648 = %w(ORDERPATCH)
+ RFC3744 = %w(ACL)
+ RFC5323 = %w(SEARCH)
+ RFC5789 = %w(PATCH)
+
+ HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789
+ HTTP_METHOD_LOOKUP = Hash.new { |h, m| h[m] = m.underscore.to_sym if HTTP_METHODS.include?(m) }
# Returns the HTTP \method that the application should see.
# In the case where the \method was overridden by a middleware
View
5 actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,6 +1,7 @@
require 'erb'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
+require 'active_support/inflector'
module ActionDispatch
module Routing
@@ -186,8 +187,8 @@ def constraints
def request_method_condition
if via = @options[:via]
- via = Array(via).map { |m| m.to_s.upcase }
- { :request_method => Regexp.union(*via) }
+ via = Array(via).map { |m| m.to_s.dasherize.upcase }
+ { :request_method => %r[^#{via.join('|')}$] }
else
{ }
end
View
2  actionpack/lib/action_dispatch/testing/test_process.rb
@@ -22,7 +22,7 @@ def flash
end
def cookies
- @request.cookies.merge(@response.cookies)
+ HashWithIndifferentAccess.new(@request.cookies.merge(@response.cookies))
end
def redirect_to_url
View
3  actionpack/lib/action_view/helpers/capture_helper.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/string/output_safety'
module ActionView
# = Action View Capture Helper
@@ -38,7 +39,7 @@ def capture(*args)
value = nil
buffer = with_output_buffer { value = yield(*args) }
if string = buffer.presence || value and string.is_a?(String)
- string
+ ERB::Util.html_escape string
end
end
View
3  actionpack/lib/action_view/helpers/form_helper.rb
@@ -294,10 +294,9 @@ module FormHelper
#
# If you don't need to attach a form to a model instance, then check out
# FormTagHelper#form_tag.
- def form_for(record, options = nil, &proc)
+ def form_for(record, options = {}, &proc)
raise ArgumentError, "Missing block" unless block_given?
- options ||= {}
options[:html] ||= {}
case record
View
31 actionpack/lib/action_view/helpers/number_helper.rb
@@ -15,7 +15,7 @@ module Helpers #:nodoc:
# unchanged if can't be converted into a valid number.
module NumberHelper
- DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :unit => "$", :separator => ".", :delimiter => ",",
+ DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
:precision => 2, :significant => false, :strip_insignificant_zeros => false }
# Raised when argument +number+ param given to the helpers is invalid and
@@ -81,15 +81,18 @@ def number_to_phone(number, options = {})
# in the +options+ hash.
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
- # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
- # * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are:
- #
- # %u The currency unit
- # %n The number
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
+ # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
+ # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
+ # * <tt>:format</tt> - Sets the format for non-negative numbers (defaults to "%u%n").
+ # Fields are <tt>%u</tt> for the currency, and <tt>%n</tt>
+ # for the number.
+ # * <tt>:negative_format</tt> - Sets the format for negative numbers (defaults to prepending
+ # an hyphen to the formatted number given by <tt>:format</tt>).
+ # Accepts the same fields than <tt>:format</tt>, except
+ # <tt>%n</tt> is here the absolute value of the number.
#
# ==== Examples
# number_to_currency(1234567890.50) # => $1,234,567,890.50
@@ -97,6 +100,8 @@ def number_to_phone(number, options = {})
# number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
# number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,506 €
#
+ # number_to_currency(1234567890.50, :negative_format => "(%u%n)")
+ # # => ($1,234,567,890.51)
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
# # => &pound;1234567890,50
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
@@ -110,11 +115,17 @@ def number_to_currency(number, options = {})
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
defaults = DEFAULT_CURRENCY_VALUES.merge(defaults).merge!(currency)
+ defaults[:negative_format] = "-" + options[:format] if options[:format]
options = defaults.merge!(options)
unit = options.delete(:unit)
format = options.delete(:format)
+ if number.to_f < 0
+ format = options.delete(:negative_format)
+ number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
+ end
+
begin
value = number_with_precision(number, options.merge(:raise => true))
format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
View
7 actionpack/lib/action_view/template/handlers/erb.rb
@@ -15,6 +15,7 @@ def <<(value)
super(value.to_s)
end
alias :append= :<<
+ alias :safe_append= :safe_concat
end
class Template
@@ -40,7 +41,11 @@ def add_expr_literal(src, code)
end
def add_expr_escaped(src, code)
- src << '@output_buffer.append= ' << escaped_expr(code) << ';'
+ if code =~ BLOCK_EXPR
+ src << "@output_buffer.safe_append= " << code
+ else
+ src << "@output_buffer.safe_concat((" << code << ").to_s);"
+ end
end
def add_postamble(src)
View
16 actionpack/test/controller/mime_responds_test.rb
@@ -487,6 +487,10 @@ def using_resource
respond_with(resource)
end
+ def using_hash_resource
+ respond_with({:result => resource})
+ end
+
def using_resource_with_block
respond_with(resource) do |format|
format.csv { render :text => "CSV" }
@@ -587,6 +591,18 @@ def test_using_resource
end
end
+ def test_using_hash_resource
+ @request.accept = "application/xml"
+ get :using_hash_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hash>\n <name>david</name>\n</hash>\n", @response.body
+
+ @request.accept = "application/json"
+ get :using_hash_resource
+ assert_equal "application/json", @response.content_type
+ assert_equal %Q[{"result":["david",13]}], @response.body
+ end
+
def test_using_resource_with_block
@request.accept = "*/*"
get :using_resource_with_block
View
7 actionpack/test/controller/new_base/bare_metal_test.rb
@@ -39,4 +39,11 @@ class HeadTest < ActiveSupport::TestCase
assert_equal 404, status
end
end
+
+ class BareControllerTest < ActionController::TestCase
+ test "GET index" do
+ get :index
+ assert_equal "Hello world", @response.body
+ end
+ end
end
View
10 actionpack/test/controller/new_base/render_template_test.rb
@@ -9,6 +9,7 @@ class WithoutLayoutController < ActionController::Base
"locals.html.erb" => "The secret is <%= secret %>",
"xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
"with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
+ "with_implicit_raw.html.erb"=> "Hello <%== '<strong>this is also raw</strong>' %>",
"test/with_json.html.erb" => "<%= render :template => 'test/with_json.json' %>",
"test/with_json.json.erb" => "<%= render :template => 'test/final' %>",
"test/final.json.erb" => "{ final: json }",
@@ -51,6 +52,10 @@ def with_raw
render :template => "with_raw"
end
+ def with_implicit_raw
+ render :template => "with_implicit_raw"
+ end
+
def with_error
render :template => "test/with_error"
end
@@ -99,6 +104,11 @@ class TestWithoutLayout < Rack::TestCase
assert_body "Hello <strong>this is raw</strong>"
assert_status 200
+
+ get :with_implicit_raw
+
+ assert_body "Hello <strong>this is also raw</strong>"
+ assert_status 200
end
test "rendering a template with renders another template with other format that renders other template in the same format" do
View
18 actionpack/test/dispatch/cookies_test.rb
@@ -94,6 +94,16 @@ def delete_cookie_with_domain
cookies.delete(:user_name, :domain => :all)
head :ok
end
+
+ def symbol_key
+ cookies[:user_name] = "david"
+ head :ok
+ end
+
+ def string_key
+ cookies['user_name'] = "david"
+ head :ok
+ end
end
tests TestController
@@ -291,6 +301,14 @@ def test_deleting_cookie_with_all_domain_option
assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
+ def test_cookies_hash_is_indifferent_access
+ [:symbol_key, :string_key].each do |cookie_key|
+ get cookie_key
+ assert_equal "david", cookies[:user_name]
+ assert_equal "david", cookies['user_name']
+ end
+ end
+
private
def assert_cookie_header(expected)
header = @response.headers["Set-Cookie"]
View
31 actionpack/test/dispatch/routing_test.rb
@@ -2255,3 +2255,34 @@ def test_default_scope
end
end
+class TestHttpMethods < ActionDispatch::IntegrationTest
+ RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
+ RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
+ RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
+ RFC3648 = %w(ORDERPATCH)
+ RFC3744 = %w(ACL)
+ RFC5323 = %w(SEARCH)
+ RFC5789 = %w(PATCH)
+
+ def simple_app(response)
+ lambda { |env| [ 200, { 'Content-Type' => 'text/plain' }, [response] ] }
+ end
+
+ setup do
+ s = self
+ @app = ActionDispatch::Routing::RouteSet.new
+
+ @app.draw do
+ (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789).each do |method|
+ match '/' => s.simple_app(method), :via => method.underscore.to_sym
+ end
+ end
+ end
+
+ (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789).each do |method|
+ test "request method #{method.underscore} can be matched" do
+ get '/', nil, 'REQUEST_METHOD' => method
+ assert_equal method, @response.body
+ end
+ end
+end
View
10 actionpack/test/template/capture_helper_test.rb
@@ -28,6 +28,16 @@ def test_capture_returns_nil_if_the_returned_value_is_not_a_string
assert_nil @av.capture { 1 }
end
+ def test_capture_escapes_html
+ string = @av.capture { '<em>bar</em>' }
+ assert_equal '&lt;em&gt;bar&lt;/em&gt;', string
+ end
+
+ def test_capture_doesnt_escape_twice
+ string = @av.capture { '&lt;em&gt;bar&lt;/em&gt;'.html_safe }
+ assert_equal '&lt;em&gt;bar&lt;/em&gt;', string
+ end
+
def test_content_for
assert ! content_for?(:title)
content_for :title, 'title'
View
5 actionpack/test/template/number_helper_i18n_test.rb
@@ -7,7 +7,7 @@ def setup
I18n.backend.store_translations 'ts',
:number => {
:format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false },
- :currency => { :format => { :unit => '&$', :format => '%u - %n', :precision => 2 } },
+ :currency => { :format => { :unit => '&$', :format => '%u - %n', :negative_format => '(%u - %n)', :precision => 2 } },
:human => {
:format => {
:precision => 2,
@@ -43,11 +43,14 @@ def setup
def test_number_to_i18n_currency
assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts'))
+ assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts'))
+ assert_equal("-10.00 - &$", number_to_currency(-10, :locale => 'ts', :format => "%n - %u"))
end
def test_number_to_currency_with_clean_i18n_settings
clean_i18n do
assert_equal("$10.00", number_to_currency(10))
+ assert_equal("-$10.00", number_to_currency(-10))
end
end
View
4 actionpack/test/template/number_helper_test.rb
@@ -45,11 +45,15 @@ def test_number_to_phone
def test_number_to_currency
assert_equal("$1,234,567,890.50", number_to_currency(1234567890.50))
assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
+ assert_equal("-$1,234,567,890.50", number_to_currency(-1234567890.50))
+ assert_equal("-$ 1,234,567,890.50", number_to_currency(-1234567890.50, {:format => "%u %n"}))
+ assert_equal("($1,234,567,890.50)", number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"}))
assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0}))
assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
+ assert_equal("1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
end
def test_number_to_percentage
View
7 actionpack/test/template/url_helper_test.rb
@@ -263,12 +263,7 @@ def test_link_to_unless
assert_equal "<strong>Showing</strong>",
link_to_unless(true, "Showing", url_hash) { |name|
- "<strong>#{name}</strong>"
- }
-
- assert_equal "<strong>Showing</strong>",
- link_to_unless(true, "Showing", url_hash) { |name|
- "<strong>#{name}</strong>"
+ "<strong>#{name}</strong>".html_safe
}
assert_equal "test",
View
7 activemodel/test/cases/serializeration/xml_serialization_test.rb
@@ -70,6 +70,13 @@ def setup
assert_match %r{<CreatedAt}, @xml
end
+ test "should allow lower-camelized tags" do
+ @xml = @contact.to_xml :root => 'xml_contact', :camelize => :lower
+ assert_match %r{^<xmlContact>}, @xml
+ assert_match %r{</xmlContact>$}, @xml
+ assert_match %r{<createdAt}, @xml
+ end
+
test "should allow skipped types" do
@xml = @contact.to_xml :skip_types => true
assert_match %r{<age>25</age>}, @xml
View
38 activerecord/lib/active_record/associations.rb
@@ -1882,8 +1882,6 @@ def initialize(base, associations, joins)
@join_parts = [JoinBase.new(base, joins)]
@associations = {}
@reflections = []
- @base_records_hash = {}
- @base_records_in_order = []
@alias_tracker = AliasTracker.new(joins)
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
build(associations)
@@ -1906,15 +1904,18 @@ def join_base
end
def instantiate(rows)
- rows.each_with_index do |row, i|
- primary_id = join_base.record_id(row)
- unless @base_records_hash[primary_id]
- @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
- end
- construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
- end
- remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations)
- return @base_records_in_order
+ primary_key = join_base.aliased_primary_key
+ parents = {}
+
+ records = rows.map { |model|
+ primary_id = model[primary_key]
+ parent = parents[primary_id] ||= join_base.instantiate(model)
+ construct(parent, @associations, join_associations.dup, model)
+ parent
+ }.uniq
+
+ remove_duplicate_results!(join_base.active_record, records, @associations)
+ records
end
def remove_duplicate_results!(base, records, associations)
@@ -2014,10 +2015,13 @@ def build_join_association(reflection, parent)
def construct(parent, associations, join_parts, row)
case associations
when Symbol, String
+ name = associations.to_s
+
join_part = join_parts.detect { |j|
- j.reflection.name.to_s == associations.to_s &&
+ j.reflection.name.to_s == name &&
j.parent_table_name == parent.class.table_name }
- raise(ConfigurationError, "No such association") if join_part.nil?
+
+ raise(ConfigurationError, "No such association") unless join_part
join_parts.delete(join_part)
construct_association(parent, join_part, row)
@@ -2027,13 +2031,7 @@ def construct(parent, associations, join_parts, row)
end
when Hash
associations.sort_by { |k,_| k.to_s }.each do |name, assoc|
- join_part = join_parts.detect{ |j|
- j.reflection.name.to_s == name.to_s &&
- j.parent_table_name == parent.class.table_name }
- raise(ConfigurationError, "No such association") if join_part.nil?
-
- association = construct_association(parent, join_part, row)
- join_parts.delete(join_part)
+ association = construct(parent, name, join_parts, row)
construct(association, assoc, join_parts, row) if association
end
else
View
2  activerecord/lib/active_record/associations/association_proxy.rb
@@ -53,7 +53,7 @@ class AssociationProxy #:nodoc:
alias_method :proxy_respond_to?, :respond_to?
alias_method :proxy_extend, :extend
delegate :to_param, :to => :proxy_target
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ }
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to_missing|proxy_/ }
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
View
2  activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -50,7 +50,7 @@ def execute(sql, name = nil)
# Executes +sql+ statement in the context of this connection using
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
- def exec(sql, name = 'SQL', binds = [])
+ def exec_query(sql, name = 'SQL', binds = [])
end
# Returns the last auto-generated ID from the affected table.
View
4 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -326,7 +326,7 @@ def clear_cache!
@statements.clear
end
- def exec(sql, name = 'SQL', binds = [])
+ def exec_query(sql, name = 'SQL', binds = [])
log(sql, name) do
result = nil
@@ -708,7 +708,7 @@ def configure_connection
def select(sql, name = nil, binds = [])
@connection.query_with_result = true
- rows = exec(sql, name, binds).to_a
+ rows = exec_query(sql, name, binds).to_a
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
rows
end
View
16 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -398,7 +398,7 @@ def quoted_date(value) #:nodoc:
# Set the authorized user for this session
def session_auth=(user)
clear_cache!
- exec "SET SESSION AUTHORIZATION #{user}"
+ exec_query "SET SESSION AUTHORIZATION #{user}"
end
# REFERENTIAL INTEGRITY ====================================
@@ -532,7 +532,7 @@ def substitute_for(column, current_values)
Arel.sql("$#{current_values.length + 1}")
end
- def exec(sql, name = 'SQL', binds = [])
+ def exec_query(sql, name = 'SQL', binds = [])
return exec_no_cache(sql, name) if binds.empty?
log(sql, name) do
@@ -714,8 +714,8 @@ def indexes(table_name, name = nil)
# Returns the list of all column definitions for a table.
def columns(table_name, name = nil)
# Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).collect do |name, type, default, notnull|
- PostgreSQLColumn.new(name, default, type, notnull == 'f')
+ column_definitions(table_name).collect do |column_name, type, default, notnull|
+ PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
end
end
@@ -932,12 +932,12 @@ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
# requires that the ORDER BY include the distinct column.
#
# distinct("posts.id", "posts.created_at desc")
- def distinct(columns, order_by) #:nodoc:
- return "DISTINCT #{columns}" if order_by.blank?
+ def distinct(columns, orders) #:nodoc:
+ return "DISTINCT #{columns}" if orders.empty?
# Construct a clean list of column names from the ORDER BY clause, removing
# any ASC/DESC modifiers
- order_columns = order_by.split(',').collect { |s| s.split.first }
+ order_columns = orders.collect { |s| s =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : s }
order_columns.delete_if { |c| c.blank? }
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
@@ -1040,7 +1040,7 @@ def last_insert_id(table, sequence_name) #:nodoc:
# Executes a SELECT query and returns the results, performing any data type
# conversions that are required to be performed here instead of in PostgreSQLColumn.
def select(sql, name = nil, binds = [])
- exec(sql, name, binds).to_a
+ exec_query(sql, name, binds).to_a
end
def select_raw(sql, name = nil)
View
22 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -143,7 +143,7 @@ def quoted_date(value) #:nodoc:
# DATABASE STATEMENTS ======================================
- def exec(sql, name = nil, binds = [])
+ def exec_query(sql, name = nil, binds = [])
log(sql, name) do
# Don't cache statements without bind values
@@ -186,7 +186,7 @@ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #
alias :create :insert_sql
def select_rows(sql, name = nil)
- exec(sql, name).rows
+ exec_query(sql, name).rows
end
def begin_db_transaction #:nodoc:
@@ -210,7 +210,7 @@ def tables(name = nil) #:nodoc:
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
SQL
- exec(sql, name).map do |row|
+ exec_query(sql, name).map do |row|
row['name']
end
end
@@ -222,12 +222,12 @@ def columns(table_name, name = nil) #:nodoc:
end
def indexes(table_name, name = nil) #:nodoc:
- exec("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
IndexDefinition.new(
table_name,
row['name'],
row['unique'] != 0,
- exec("PRAGMA index_info('#{row['name']}')").map { |col|
+ exec_query("PRAGMA index_info('#{row['name']}')").map { |col|
col['name']
})
end
@@ -241,11 +241,11 @@ def primary_key(table_name) #:nodoc:
end
def remove_index!(table_name, index_name) #:nodoc:
- exec "DROP INDEX #{quote_column_name(index_name)}"
+ exec_query "DROP INDEX #{quote_column_name(index_name)}"
end
def rename_table(name, new_name)
- exec "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
+ exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
# See: http://www.sqlite.org/lang_altertable.html
@@ -282,7 +282,7 @@ def change_column_default(table_name, column_name, default) #:nodoc:
def change_column_null(table_name, column_name, null, default = nil)
unless null || default.nil?
- exec("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
alter_table(table_name) do |definition|
definition[column_name].null = null
@@ -314,7 +314,7 @@ def empty_insert_statement_value
protected
def select(sql, name = nil, binds = []) #:nodoc:
- result = exec(sql, name, binds)
+ result = exec_query(sql, name, binds)
columns = result.columns.map { |column|
column.sub(/^"?\w+"?\./, '')
}
@@ -399,11 +399,11 @@ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
quoted_to = quote_table_name(to)
- exec("SELECT * FROM #{quote_table_name(from)}").each do |row|
+ exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
sql << ')'
- exec sql
+ exec_query sql
end
end
View
10 activerecord/lib/active_record/relation/finder_methods.rb
@@ -171,14 +171,16 @@ def all(*args)
def exists?(id = nil)
id = id.id if ActiveRecord::Base === id
+ relation = select(primary_key).limit(1)
+
case id
when Array, Hash
- where(id).exists?
+ relation = relation.where(id)
else
- relation = select(primary_key).limit(1)
relation = relation.where(primary_key.eq(id)) if id
- relation.first ? true : false
end
+
+ relation.first ? true : false
end
protected
@@ -222,7 +224,7 @@ def apply_join_dependency(relation, join_dependency)
end
def construct_limited_ids_condition(relation)
- orders = relation.order_values.join(", ")
+ orders = relation.order_values
values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders)
ids_array = relation.select(values).collect {|row| row[@klass.primary_key]}
View
2  activerecord/lib/active_record/timestamp.rb
@@ -61,7 +61,7 @@ def update(*args) #:nodoc:
end
def should_record_timestamps?
- record_timestamps && (!partial_updates? || changed?)
+ record_timestamps && (!partial_updates? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
end
def timestamp_attributes_for_update_in_model
View
42 activerecord/lib/active_record/validations/uniqueness.rb
@@ -154,33 +154,25 @@ module ClassMethods
# | # title!
#
# This could even happen if you use transactions with the 'serializable'
- # isolation level. There are several ways to get around this problem:
+ # isolation level. The best way to work around this problem is to add a unique
+ # index to the database table using
+ # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
+ # rare case that a race condition occurs, the database will guarantee
+ # the field's uniqueness.
#
- # - By locking the database table before validating, and unlocking it after
- # saving. However, table locking is very expensive, and thus not
- # recommended.
- # - By locking a lock file before validating, and unlocking it after saving.
- # This does not work if you've scaled your Rails application across
- # multiple web servers (because they cannot share lock files, or cannot
- # do that efficiently), and thus not recommended.
- # - Creating a unique index on the field, by using
- # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
- # rare case that a race condition occurs, the database will guarantee
- # the field's uniqueness.
+ # When the database catches such a duplicate insertion,
+ # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
+ # exception. You can either choose to let this error propagate (which
+ # will result in the default Rails exception page being shown), or you
+ # can catch it and restart the transaction (e.g. by telling the user
+ # that the title already exists, and asking him to re-enter the title).
+ # This technique is also known as optimistic concurrency control:
+ # http://en.wikipedia.org/wiki/Optimistic_concurrency_control
#
- # When the database catches such a duplicate insertion,
- # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
- # exception. You can either choose to let this error propagate (which
- # will result in the default Rails exception page being shown), or you
- # can catch it and restart the transaction (e.g. by telling the user
- # that the title already exists, and asking him to re-enter the title).
- # This technique is also known as optimistic concurrency control:
- # http://en.wikipedia.org/wiki/Optimistic_concurrency_control
- #
- # Active Record currently provides no way to distinguish unique
- # index constraint errors from other types of database errors, so you
- # will have to parse the (database-specific) exception message to detect
- # such a case.
+ # Active Record currently provides no way to distinguish unique
+ # index constraint errors from other types of database errors, so you
+ # will have to parse the (database-specific) exception message to detect
+ # such a case.
#
def validates_uniqueness_of(*attr_names)
validates_with UniquenessValidator, _merge_attributes(attr_names)
View
26 activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -49,18 +49,18 @@ def test_bind_value_substitute
end
def test_exec_no_binds
- @connection.exec('drop table if exists ex')
- @connection.exec(<<-eosql)
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query(<<-eosql)
CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
`data` varchar(255))
eosql
- result = @connection.exec('SELECT id, data FROM ex')
+ result = @connection.exec_query('SELECT id, data FROM ex')
assert_equal 0, result.rows.length
assert_equal 2, result.columns.length
assert_equal %w{ id data }, result.columns
- @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @connection.exec('SELECT id, data FROM ex')
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @connection.exec_query('SELECT id, data FROM ex')
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -68,13 +68,13 @@ def test_exec_no_binds
end
def test_exec_with_binds
- @connection.exec('drop table if exists ex')
- @connection.exec(<<-eosql)
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query(<<-eosql)
CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
`data` varchar(255))
eosql
- @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @connection.exec(
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @connection.exec_query(
'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
assert_equal 1, result.rows.length
@@ -84,15 +84,15 @@ def test_exec_with_binds
end
def test_exec_typecasts_bind_vals
- @connection.exec('drop table if exists ex')
- @connection.exec(<<-eosql)
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query(<<-eosql)
CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
`data` varchar(255))
eosql
- @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
column = @connection.columns('ex').find { |col| col.name == 'id' }
- result = @connection.exec(
+ result = @connection.exec_query(
'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
assert_equal 1, result.rows.length
View
18 activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -5,8 +5,8 @@ module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- @connection.exec('drop table if exists ex')
- @connection.exec('create table ex(id serial primary key, data character varying(255))')
+ @connection.exec_query('drop table if exists ex')
+ @connection.exec_query('create table ex(id serial primary key, data character varying(255))')
end
def test_table_alias_length
@@ -16,14 +16,14 @@ def test_table_alias_length
end
def test_exec_no_binds
- result = @connection.exec('SELECT id, data FROM ex')
+ result = @connection.exec_query('SELECT id, data FROM ex')
assert_equal 0, result.rows.length
assert_equal 2, result.columns.length
assert_equal %w{ id data }, result.columns
string = @connection.quote('foo')
- @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})")
- result = @connection.exec('SELECT id, data FROM ex')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ result = @connection.exec_query('SELECT id, data FROM ex')
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -32,8 +32,8 @@ def test_exec_no_binds
def test_exec_with_binds
string = @connection.quote('foo')
- @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})")
- result = @connection.exec(
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ result = @connection.exec_query(
'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]])
assert_equal 1, result.rows.length
@@ -44,10 +44,10 @@ def test_exec_with_binds
def test_exec_typecasts_bind_vals
string = @connection.quote('foo')
- @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
column = @connection.columns('ex').find { |col| col.name == 'id' }
- result = @connection.exec(
+ result = @connection.exec_query(
'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']])
assert_equal 1, result.rows.length
View
4 activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -55,7 +55,7 @@ def test_setting_auth_clears_stmt_cache
set_session_auth
USERS.each do |u|
set_session_auth u
- assert_equal u, @connection.exec("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
+ assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
set_session_auth
end
end
@@ -67,7 +67,7 @@ def test_auth_with_bind
USERS.each do |u|
@connection.clear_cache!
set_session_auth u
- assert_equal u, @connection.exec("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
+ assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
set_session_auth
end
end
View
24 activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -58,24 +58,24 @@ def test_bind_value_substitute
end
def test_exec_no_binds
- @conn.exec('create table ex(id int, data string)')
- result = @conn.exec('SELECT id, data FROM ex')
+ @conn.exec_query('create table ex(id int, data string)')
+ result = @conn.exec_query('SELECT id, data FROM ex')
assert_equal 0, result.rows.length
assert_equal 2, result.columns.length
assert_equal %w{ id data }, result.columns
- @conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @conn.exec('SELECT id, data FROM ex')
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @conn.exec_query('SELECT id, data FROM ex')
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
assert_equal [[1, 'foo']], result.rows
end
- def test_exec_with_binds
- @conn.exec('create table ex(id int, data string)')
- @conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @conn.exec(
+ def test_exec_query_with_binds
+ @conn.exec_query('create table ex(id int, data string)')
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @conn.exec_query(
'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
assert_equal 1, result.rows.length
@@ -84,12 +84,12 @@ def test_exec_with_binds
assert_equal [[1, 'foo']], result.rows
end
- def test_exec_typecasts_bind_vals
- @conn.exec('create table ex(id int, data string)')
- @conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ def test_exec_query_typecasts_bind_vals
+ @conn.exec_query('create table ex(id int, data string)')
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
column = @conn.columns('ex').find { |col| col.name == 'id' }
- result = @conn.exec(
+ result = @conn.exec_query(
'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
assert_equal 1, result.rows.length
View
2  activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -138,7 +138,7 @@ def test_eager_association_loading_with_belongs_to_sti
end
def test_eager_association_loading_with_multiple_stis_and_order
- author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
+ author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :conditions => 'posts.id = 4')
assert_equal authors(:david), author
assert_no_queries do
author.posts.first.special_comments
View
4 activerecord/test/cases/associations/eager_test.rb
@@ -584,8 +584,8 @@ def test_limited_eager_with_order
end
def test_limited_eager_with_multiple_order_columns
- assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title), posts.id', :limit => 2, :offset => 1)
- assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC, posts.id', :limit => 2, :offset => 1)
+ assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1)
+ assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1)
end
def test_limited_eager_with_numeric_in_association
View
2  activerecord/test/cases/base_test.rb
@@ -1353,7 +1353,7 @@ def test_inspect_class
def test_inspect_instance
topic = topics(:first)
- assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil>), topic.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
end
def test_inspect_new_instance
View
14 activerecord/test/cases/dirty_test.rb
@@ -395,6 +395,20 @@ def test_save_should_store_serialized_attributes_even_with_partial_updates
end
end
+ def test_save_always_should_update_timestamps_when_serialized_attributes_are_present
+ with_partial_updates(Topic) do
+ topic = Topic.create!(:content => {:a => "a"})
+ topic.save!
+
+ updated_at = topic.updated_at
+ topic.content[:hello] = 'world'
+ topic.save!
+
+ assert_not_equal updated_at, topic.updated_at
+ assert_equal 'world', topic.content[:hello]
+ end
+ end
+
def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present
with_partial_updates(Topic) do
Topic.create!(:author_name => 'Bill', :content => {:a => "a"})
View
10 activerecord/test/cases/helper.rb
@@ -48,6 +48,10 @@ def with_active_record_default_timezone(zone)
ActiveRecord::Base.connection.class.class_eval do
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/]
+ # FIXME: this needs to be refactored so specific database can add their own
+ # ignored SQL. This ignored SQL is for Oracle.
+ IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from ((all|user)_tab_columns|(all|user)_triggers|(all|user)_constraints)/im]
+
def execute_with_query_record(sql, name = nil, &block)
$queries_executed ||= []
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
@@ -56,13 +60,13 @@ def execute_with_query_record(sql, name = nil, &block)
alias_method_chain :execute, :query_record
- def exec_with_query_record(sql, name = nil, binds = [], &block)
+ def exec_query_with_query_record(sql, name = nil, binds = [], &block)
$queries_executed ||= []
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
- exec_without_query_record(sql, name, binds, &block)
+ exec_query_without_query_record(sql, name, binds, &block)
end
- alias_method_chain :exec, :query_record
+ alias_method_chain :exec_query, :query_record
end
ActiveRecord::Base.connection.class.class_eval {
View
10 activerecord/test/cases/reflection_test.rb
@@ -34,25 +34,25 @@ def test_human_name
def test_read_attribute_names
assert_equal(
- %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type ).sort,
+ %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type created_at updated_at ).sort,
@first.attribute_names
)
end
def test_columns
- assert_equal 14, Topic.columns.length
+ assert_equal 16, Topic.columns.length
end
def test_columns_are_returned_in_the_order_they_were_declared
column_names = Topic.columns.map { |column| column.name }
- assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group), column_names
+ assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group created_at updated_at), column_names
end
def test_content_columns
content_columns = Topic.content_columns
content_column_names = content_columns.map {|column| column.name}
- assert_equal 10, content_columns.length
- assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title).sort, content_column_names.sort
+ assert_equal 12, content_columns.length
+ assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title created_at updated_at).sort, content_column_names.sort
end
def test_column_string_type_and_limit
View
13 activerecord/test/cases/relations_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/tag'
require 'models/tagging'
require 'models/post'
require 'models/topic'
@@ -17,7 +18,7 @@
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
- :taggings, :cars
+ :tags, :taggings, :cars
def test_bind_values
relation = Post.scoped
@@ -144,6 +145,16 @@ def test_finding_with_order_and_take
assert_equal entrants(:first).name, entrants.first.name
end
+ def test_finding_with_complex_order_and_limit
+ tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
+ assert_equal 1, tags.length
+ end
+
+ def test_finding_with_complex_order
+ tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
+ assert_equal 3, tags.length
+ end
+
def test_finding_with_order_limit_and_offset
entrants = Entrant.order("id ASC").limit(2).offset(1)
View
12 activerecord/test/connections/native_oracle/connection.rb
@@ -33,15 +33,3 @@
ActiveRecord::Base.establish_connection 'arunit'
Course.establish_connection 'arunit2'
-# for assert_queries test helper
-ActiveRecord::Base.connection.class.class_eval do
- IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from ((all|user)_tab_columns|(all|user)_triggers|(all|user)_constraints)/im]
-
- def select_with_query_record(sql, name = nil)
- $queries_executed ||= []
- $queries_executed << sql unless IGNORED_SELECT_SQL.any? { |r| sql =~ r }
- select_without_query_record(sql, name)
- end
-
- alias_method_chain :select, :query_record
-end
View
1  activerecord/test/schema/schema.rb
@@ -565,6 +565,7 @@ def create_table(*args, &block)
t.string :parent_title
t.string :type
t.string :group
+ t.timestamps
end
create_table :toys, :primary_key => :toy_id ,:force => true do |t|
View
7 activesupport/lib/active_support/cache/file_store.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
+require 'rack/utils'
module ActiveSupport
module Cache
@@ -11,8 +12,6 @@ class FileStore < Store
attr_reader :cache_path
DIR_FORMATTER = "%03X"
- ESCAPE_FILENAME_CHARS = /[^a-z0-9_.-]/i
- UNESCAPE_FILENAME_CHARS = /%[0-9A-F]{2}/
def initialize(cache_path, options = nil)
super(options)
@@ -136,7 +135,7 @@ def lock_file(file_name, &block) # :nodoc:
# Translate a key into a file path.
def key_file_path(key)
- fname = key.to_s.gsub(ESCAPE_FILENAME_CHARS){|match| "%#{match.ord.to_s(16).upcase}"}
+ fname = Rack::Utils.escape(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
dir_2 = hash.modulo(0x1000)
@@ -156,7 +155,7 @@ def key_file_path(key)
# Translate a file path into a key.
def file_path_key(path)
fname = path[cache_path.size, path.size].split(File::SEPARATOR, 4).last
- fname.gsub(UNESCAPE_FILENAME_CHARS){|match| $1.ord.to_s(16)}
+ Rack::Utils.unescape(fname)
end
# Delete empty directories in the cache.
View
6 activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -28,7 +28,7 @@ def default(key = nil)
end
def self.new_from_hash_copying_default(hash)
- ActiveSupport::HashWithIndifferentAccess.new(hash).tap do |new_hash|
+ new(hash).tap do |new_hash|
new_hash.default = hash.default
end
end
@@ -97,7 +97,9 @@ def values_at(*indices)
# Returns an exact copy of the hash.
def dup
- HashWithIndifferentAccess.new(self)
+ self.class.new(self).tap do |new_hash|
+ new_hash.default = default
+ end
end
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
View
2  activesupport/lib/active_support/ordered_hash.rb
@@ -137,6 +137,8 @@ def each
alias_method :each_pair, :each
+ alias_method :select, :find_all
+
def clear
super
@keys.clear
View
6 activesupport/lib/active_support/xml_mini.rb
@@ -126,9 +126,11 @@ def to_tag(key, value, options)
end
def rename_key(key, options = {})
- camelize = options.has_key?(:camelize) && options[:camelize]
+ camelize = options[:camelize]
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
- key = key.camelize if camelize
+ if camelize
+ key = true == camelize ? key.camelize : key.camelize(camelize)
+ end
key = _dasherize(key) if dasherize
key
end
View
9 activesupport/test/caching_test.rb
@@ -356,9 +356,13 @@ module CacheDeleteMatchedBehavior
def test_delete_matched
@cache.write("foo", "bar")
@cache.write("fu", "baz")
+ @cache.write("foo/bar", "baz")
+ @cache.write("fu/baz", "bar")
@cache.delete_matched(/oo/)
assert_equal false, @cache.exist?("foo")
assert_equal true, @cache.exist?("fu")
+ assert_equal false, @cache.exist?("foo/bar")
+ assert_equal true, @cache.exist?("fu/baz")
end
end
@@ -509,6 +513,11 @@ def test_deprecated_expires_in_on_read
assert_nil old_cache.read('foo')
end
end
+
+ def test_key_transformation
+ key = @cache.send(:key_file_path, "views/index?id=1")
+ assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
+ end
end
class MemoryStoreTest < ActiveSupport::TestCase
View
24 activesupport/test/core_ext/hash_ext_test.rb
@@ -6,6 +6,9 @@
require 'active_support/core_ext/object/conversions'
class HashExtTest < Test::Unit::TestCase
+ class IndifferentHash < HashWithIndifferentAccess
+ end
+
def setup
@strings = { 'a' => 1, 'b' => 2 }
@symbols = { :a => 1, :b => 2 }
@@ -267,7 +270,6 @@ def test_to_options_on_indifferent_preserves_hash
assert_equal 1, h['first']
end
-
def test_indifferent_subhashes
h = {'user' => {'id' => 5}}.with_indifferent_access
['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
@@ -276,6 +278,17 @@ def test_indifferent_subhashes
['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
end
+ def test_indifferent_duplication
+ # Should preserve default value
+ h = HashWithIndifferentAccess.new
+ h.default = '1234'
+ assert_equal h.default, h.dup.default
+
+ # Should preserve class for subclasses
+ h = IndifferentHash.new
+ assert_equal h.class, h.dup.class
+ end
+
def test_assert_valid_keys
assert_nothing_raised do
{ :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
@@ -486,7 +499,7 @@ def test_to_param_hash
def test_to_param_hash_escapes_its_keys_and_values
assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param
end
-
+
def test_to_param_orders_by_key_in_ascending_order
assert_equal 'a=2&b=1&c=0', ActiveSupport::OrderedHash[*%w(b 1 c 0 a 2)].to_param
end
@@ -525,6 +538,13 @@ def test_one_level_camelize_true
assert xml.include?(%(<Name>David</Name>))
end
+ def test_one_level_camelize_lower
+ xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => :lower))
+ assert_equal "<person>", xml.first(8)
+ assert xml.include?(%(<streetName>Paulina</streetName>))
+ assert xml.include?(%(<name>David</name>))
+ end
+
def test_one_level_with_types
xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options)
assert_equal "<person>", xml.first(8)
View
8 activesupport/test/ordered_hash_test.rb
@@ -109,6 +109,14 @@ def test_each_pair
assert_equal @keys, keys
end
+ def test_find_all
+ assert_equal @keys, @ordered_hash.find_all { true }.map(&:first)
+ end
+
+ def test_select
+ assert_equal @keys, @ordered_hash.select { true }.map(&:first)
+ end
+
def test_delete_if
copy = @ordered_hash.dup
copy.delete('pink')
View
16 activesupport/test/test_xml_mini.rb
@@ -14,14 +14,26 @@ def test_rename_key_does_nothing_with_dasherize_false
assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false)
end
- def test_rename_key_camelizes_with_camelize_true
- assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true)
+ def test_rename_key_camelizes_with_camelize_false
+ assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => false)
+ end
+
+ def test_rename_key_camelizes_with_camelize_nil
+ assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => nil)
end
def test_rename_key_camelizes_with_camelize_true
assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true)
end
+ def test_rename_key_lower_camelizes_with_camelize_lower
+ assert_equal "myKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :lower)
+ end
+
+ def test_rename_key_lower_camelizes_with_camelize_upper
+ assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :upper)
+ end
+
def test_rename_key_does_not_dasherize_leading_underscores
assert_equal "_id", ActiveSupport::XmlMini.rename_key("_id")
end
View
2  railties/railties.gemspec
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
s.has_rdoc = false
s.add_dependency('rake', '>= 0.8.7')
- s.add_dependency('thor', '~> 0.14.3')
+ s.add_dependency('thor', '~> 0.14.4')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
end
Please sign in to comment.
Something went wrong with that request. Please try again.