diff options
185 files changed, 1426 insertions, 210 deletions
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 90503edc20..0c669e2e91 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -7,6 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Email composition, delivery, and receiving framework (part of Rails).' s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.' s.required_ruby_version = '>= 1.9.3' + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index e625bbe7af..920858d8c0 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,17 @@ ## Rails 4.0.0 (unreleased) ## +* Add `week_field`, `week_field_tag`, `month_field`, `month_field_tag`, `datetime_local_field`, + `datetime_local_field_tag`, `datetime_field` and `datetime_field_tag` helpers. *Carlos Galdino* + +* Add `color_field` and `color_field_tag` helpers. *Carlos Galdino* + +* `assert_generates`, `assert_recognizes`, and `assert_routing` all raise + `Assertion` instead of `RoutingError` *David Chelimsky* + +* URL path parameters with invalid encoding now raise ActionController::BadRequest. *Andrew White* + +* Malformed query and request parameter hashes now raise ActionController::BadRequest. *Andrew White* + * Add `divider` option to `grouped_options_for_select` to generate a separator `optgroup` automatically, and deprecate `prompt` as third argument, in favor of using an options hash. *Nicholas Greenfield* diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 589a67dc02..ae26d6f9e5 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -7,6 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.' s.required_ruby_version = '>= 1.9.3' + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/actionpack/examples/performance.rb b/actionpack/examples/performance.rb index 994e745bb0..8ea4758961 100644 --- a/actionpack/examples/performance.rb +++ b/actionpack/examples/performance.rb @@ -166,15 +166,7 @@ def run_all!(times, verbose) Runner.run(:diff_100, times, verbose) end -unless ENV["PROFILE"] - run_all!(1, false) - - (ENV["M"] || 1).to_i.times do - $ran = [] - run_all!(N, true) - Runner.done - end -else +if ENV["PROFILE"] Runner.run(ENV["PROFILE"].to_sym, 1, false) require "ruby-prof" RubyProf.start @@ -182,4 +174,12 @@ else result = RubyProf.stop printer = RubyProf::CallStackPrinter.new(result) printer.print(File.open("output.html", "w")) +else + run_all!(1, false) + + (ENV["M"] || 1).to_i.times do + $ran = [] + run_all!(N, true) + Runner.done + end end
\ No newline at end of file diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index bc9f6fc3e8..c1b3994035 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -203,8 +203,7 @@ module AbstractController include Rendering included do - class_attribute :_layout, :_layout_conditions, - :instance_reader => false, :instance_writer => false + class_attribute :_layout, :_layout_conditions, :instance_accessor => false self._layout = nil self._layout_conditions = {} _write_layout_method diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 80901b8bf3..0238135bc1 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -47,7 +47,7 @@ module ActionController #:nodoc: # And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a # proc that specifies when the action should be cached. # - # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time + # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time # interval (in seconds) to schedule expiration of the cached item. # # The following example depicts some of the points made above: @@ -178,8 +178,9 @@ module ActionController #:nodoc: private def normalize!(path) + ext = URI.parser.escape(extension) if extension path << 'index' if path[-1] == ?/ - path << ".#{extension}" if extension and !path.split('?', 2).first.ends_with?(".#{extension}") + path << ".#{ext}" if extension and !path.split('?', 2).first.ends_with?(".#{ext}") URI.parser.unescape(path) end end diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 11aa393bf9..0fb419f941 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -33,9 +33,7 @@ module ActionController end def send_file(event) - message = "Sent file %s" - message << " (%.1fms)" - info(message % [event.payload[:path], event.duration]) + info("Sent file %s (%.1fms)" % [event.payload[:path], event.duration]) end def redirect_to(event) diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 90648c37ad..8fd8f4797c 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -2,6 +2,9 @@ module ActionController class ActionControllerError < StandardError #:nodoc: end + class BadRequest < ActionControllerError #:nodoc: + end + class RenderError < ActionControllerError #:nodoc: end @@ -38,7 +41,7 @@ module ActionController class UnknownHttpMethod < ActionControllerError #:nodoc: end - + class UnknownFormat < ActionControllerError #:nodoc: end end diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 1f52c164de..aa67fa7f23 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -193,7 +193,8 @@ module ActionController def process_action(*args) if _wrapper_enabled? wrapped_hash = _wrap_parameters request.request_parameters - wrapped_filtered_hash = _wrap_parameters request.filtered_parameters + wrapped_keys = request.request_parameters.keys + wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys) # This will make the wrapped hash accessible from controller and view request.parameters.merge! wrapped_hash diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 56908b5794..aa5ba3e8a5 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -231,17 +231,24 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET - @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {}) + begin + @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {}) + rescue TypeError => e + raise ActionController::BadRequest, "Invalid query parameters: #{e.message}" + end end alias :query_parameters :GET # Override Rack's POST method to support indifferent access def POST - @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {}) + begin + @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {}) + rescue TypeError => e + raise ActionController::BadRequest, "Invalid request parameters: #{e.message}" + end end alias :request_parameters :POST - # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. def authorization diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index a8f49bd3bd..7349b578d2 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -12,7 +12,8 @@ module ActionDispatch 'ActionController::MethodNotAllowed' => :method_not_allowed, 'ActionController::NotImplemented' => :not_implemented, 'ActionController::UnknownFormat' => :not_acceptable, - 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity + 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionController::BadRequest' => :bad_request ) cattr_accessor :rescue_templates diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 4ad7071820..d8bcc28613 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -87,6 +87,14 @@ module ActionDispatch alias :key? :has_key? alias :include? :has_key? + def keys + @delegate.keys + end + + def values + @delegate.values + end + def []=(key, value) load_for_write! @delegate[key.to_s] = value diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index b3823bb496..205ff44b1c 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -2,6 +2,7 @@ require 'action_dispatch/http/request' require 'active_support/core_ext/uri' require 'active_support/core_ext/array/extract_options' require 'rack/utils' +require 'action_controller/metal/exceptions' module ActionDispatch module Routing @@ -16,6 +17,14 @@ module ActionDispatch def call(env) req = Request.new(env) + # If any of the path parameters has a invalid encoding then + # raise since it's likely to trigger errors further on. + req.symbolized_path_parameters.each do |key, value| + unless value.valid_encoding? + raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" + end + end + uri = URI.parse(path(req.symbolized_path_parameters, req)) uri.scheme ||= req.scheme uri.host ||= req.host diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0ae668d42a..7872f4007e 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -26,6 +26,15 @@ module ActionDispatch def call(env) params = env[PARAMETERS_KEY] + + # If any of the path parameters has a invalid encoding then + # raise since it's likely to trigger errors further on. + params.each do |key, value| + unless value.valid_encoding? + raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" + end + end + prepare_params!(params) # Just raise undefined constant errors if a controller was specified as default. @@ -654,9 +663,13 @@ module ActionDispatch dispatcher = dispatcher.app end - if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false) - dispatcher.prepare_params!(params) - return params + if dispatcher.is_a?(Dispatcher) + if dispatcher.controller(params, false) + dispatcher.prepare_params!(params) + return params + else + raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller" + end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 3d121b6b9c..b4c8f839ac 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -28,7 +28,7 @@ module ActionDispatch assert @response.send("#{type}?"), message else code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type] - assert_equal @response.response_code, code, message + assert_equal code, @response.response_code, message end else assert_equal type, @response.response_code, message diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 567ca0c392..41fa3a4b95 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -69,11 +69,9 @@ module ActionDispatch # assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" } def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) if expected_path =~ %r{://} - begin + fail_on(URI::InvalidURIError) do uri = URI.parse(expected_path) expected_path = uri.path.to_s.empty? ? "/" : uri.path - rescue URI::InvalidURIError => e - raise ActionController::RoutingError, e.message end else expected_path = "/#{expected_path}" unless expected_path.first == '/' @@ -189,14 +187,12 @@ module ActionDispatch request = ActionController::TestRequest.new if path =~ %r{://} - begin + fail_on(URI::InvalidURIError) do uri = URI.parse(path) request.env["rack.url_scheme"] = uri.scheme || "http" request.host = uri.host if uri.host request.port = uri.port if uri.port request.path = uri.path.to_s.empty? ? "/" : uri.path - rescue URI::InvalidURIError => e - raise ActionController::RoutingError, e.message end else path = "/#{path}" unless path.first == "/" @@ -205,11 +201,21 @@ module ActionDispatch request.request_method = method if method - params = @routes.recognize_path(path, { :method => method, :extras => extras }) + params = fail_on(ActionController::RoutingError) do + @routes.recognize_path(path, { :method => method, :extras => extras }) + end request.path_parameters = params.with_indifferent_access request end + + def fail_on(exception_class) + begin + yield + rescue exception_class => e + raise MiniTest::Assertion, e.message + end + end end end end diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index d04be2099c..a86b510719 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -11,7 +11,7 @@ module ActionDispatch end def initialize(env = {}) - env = Rails.application.env_config.merge(env) if defined?(Rails.application) + env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application super(DEFAULT_ENV.merge(env)) self.host = 'test.host' diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index fc81bcea80..ac150882b1 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -939,6 +939,15 @@ module ActionView Tags::RadioButton.new(object_name, method, self, tag_value, options).render end + # Returns a text_field of type "color". + # + # color_field("car", "color") + # # => <input id="car_color" name="car[color]" type="color" value="#000000" /> + # + def color_field(object_name, method, options = {}) + Tags::ColorField.new(object_name, method, self, options).render + end + # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by # some browsers. @@ -1007,6 +1016,74 @@ module ActionView Tags::TimeField.new(object_name, method, self, options).render end + # Returns a text_field of type "datetime". + # + # datetime_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="datetime" /> + # + # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. + # + # @user.born_on = Date.new(1984, 1, 12) + # datetime_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" /> + # + def datetime_field(object_name, method, options = {}) + Tags::DatetimeField.new(object_name, method, self, options).render + end + + # Returns a text_field of type "datetime-local". + # + # datetime_local_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" /> + # + # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. + # + # @user.born_on = Date.new(1984, 1, 12) + # datetime_local_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" /> + # + def datetime_local_field(object_name, method, options = {}) + Tags::DatetimeLocalField.new(object_name, method, self, options).render + end + + # Returns a text_field of type "month". + # + # month_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="month" /> + # + # The default value is generated by trying to call +strftime+ with "%Y-%m" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. + # + # @user.born_on = Date.new(1984, 1, 27) + # month_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" /> + # + def month_field(object_name, method, options = {}) + Tags::MonthField.new(object_name, method, self, options).render + end + + # Returns a text_field of type "week". + # + # week_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="week" /> + # + # The default value is generated by trying to call +strftime+ with "%Y-W%W" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. + # + # @user.born_on = Date.new(1984, 5, 12) + # week_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" /> + # + def week_field(object_name, method, options = {}) + Tags::WeekField.new(object_name, method, self, options).render + end + # Returns a text_field of type "url". # # url_field("user", "homepage") diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index e65b4e3e95..1a0019a48c 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -524,6 +524,14 @@ module ActionView output.safe_concat("</fieldset>") end + # Creates a text field of type "color". + # + # ==== Options + # * Accepts the same options as text_field_tag. + def color_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "color")) + end + # Creates a text field of type "search". # # ==== Options @@ -560,6 +568,50 @@ module ActionView text_field_tag(name, value, options.stringify_keys.update("type" => "time")) end + # Creates a text field of type "datetime". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def datetime_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "datetime")) + end + + # Creates a text field of type "datetime-local". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def datetime_local_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local")) + end + + # Creates a text field of type "month". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def month_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "month")) + end + + # Creates a text field of type "week". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def week_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "week")) + end + # Creates a text field of type "url". # # ==== Options diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index 498be596ad..9572f1c192 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -41,7 +41,7 @@ module ActionView # thus accessed as <tt>dataset.userId</tt>. # # Values are encoded to JSON, with the exception of strings and symbols. - # This may come in handy when using jQuery's HTML5-aware <tt>.data()<tt> + # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> # from 1.4.3. # # ==== Examples diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb index 5cd77c8ec3..a05e16979a 100644 --- a/actionpack/lib/action_view/helpers/tags.rb +++ b/actionpack/lib/action_view/helpers/tags.rb @@ -8,14 +8,18 @@ module ActionView autoload :CollectionCheckBoxes autoload :CollectionRadioButtons autoload :CollectionSelect + autoload :ColorField autoload :DateField autoload :DateSelect + autoload :DatetimeField + autoload :DatetimeLocalField autoload :DatetimeSelect autoload :EmailField autoload :FileField autoload :GroupedCollectionSelect autoload :HiddenField autoload :Label + autoload :MonthField autoload :NumberField autoload :PasswordField autoload :RadioButton @@ -29,6 +33,7 @@ module ActionView autoload :TimeSelect autoload :TimeZoneSelect autoload :UrlField + autoload :WeekField end end end diff --git a/actionpack/lib/action_view/helpers/tags/color_field.rb b/actionpack/lib/action_view/helpers/tags/color_field.rb new file mode 100644 index 0000000000..6f08f8483a --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/color_field.rb @@ -0,0 +1,25 @@ +module ActionView + module Helpers + module Tags + class ColorField < TextField #:nodoc: + def render + options = @options.stringify_keys + options["value"] = @options.fetch("value") { validate_color_string(value(object)) } + @options = options + super + end + + private + + def validate_color_string(string) + regex = /#[0-9a-fA-F]{6}/ + if regex.match(string) + string.downcase + else + "#000000" + end + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb index 0e79609d52..64c29dea3d 100644 --- a/actionpack/lib/action_view/helpers/tags/date_field.rb +++ b/actionpack/lib/action_view/helpers/tags/date_field.rb @@ -1,13 +1,12 @@ module ActionView module Helpers module Tags - class DateField < TextField #:nodoc: - def render - options = @options.stringify_keys - options["value"] = @options.fetch("value") { value(object).try(:to_date) } - @options = options - super - end + class DateField < DatetimeField #:nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-%m-%d") + end end end end diff --git a/actionpack/lib/action_view/helpers/tags/datetime_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_field.rb new file mode 100644 index 0000000000..e407146e96 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/datetime_field.rb @@ -0,0 +1,22 @@ +module ActionView + module Helpers + module Tags + class DatetimeField < TextField #:nodoc: + def render + options = @options.stringify_keys + options["value"] = @options.fetch("value") { format_date(value(object)) } + options["min"] = format_date(options["min"]) + options["max"] = format_date(options["max"]) + @options = options + super + end + + private + + def format_date(value) + value.try(:strftime, "%Y-%m-%dT%T.%L%z") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb new file mode 100644 index 0000000000..6668d6d718 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb @@ -0,0 +1,19 @@ +module ActionView + module Helpers + module Tags + class DatetimeLocalField < DatetimeField #:nodoc: + class << self + def field_type + @field_type ||= "datetime-local" + end + end + + private + + def format_date(value) + value.try(:strftime, "%Y-%m-%dT%T") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/month_field.rb b/actionpack/lib/action_view/helpers/tags/month_field.rb new file mode 100644 index 0000000000..3d3c32d847 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/month_field.rb @@ -0,0 +1,13 @@ +module ActionView + module Helpers + module Tags + class MonthField < DatetimeField #:nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-%m") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb index 271dc00c54..a3941860c9 100644 --- a/actionpack/lib/action_view/helpers/tags/time_field.rb +++ b/actionpack/lib/action_view/helpers/tags/time_field.rb @@ -1,13 +1,12 @@ module ActionView module Helpers module Tags - class TimeField < TextField #:nodoc: - def render - options = @options.stringify_keys - options["value"] = @options.fetch("value") { value(object).try(:strftime, "%T.%L") } - @options = options - super - end + class TimeField < DatetimeField #:nodoc: + private + + def format_date(value) + value.try(:strftime, "%T.%L") + end end end end diff --git a/actionpack/lib/action_view/helpers/tags/week_field.rb b/actionpack/lib/action_view/helpers/tags/week_field.rb new file mode 100644 index 0000000000..1e13939a0a --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/week_field.rb @@ -0,0 +1,13 @@ +module ActionView + module Helpers + module Tags + class WeekField < DatetimeField #:nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-W%W") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 8171bea8ed..552c9ba660 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -64,7 +64,7 @@ module ActionView # Delegates to <tt>I18n.localize</tt> with no additional functionality. # - # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize + # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize # for more information. def localize(*args) I18n.localize(*args) @@ -96,7 +96,7 @@ module ActionView new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) } break else - new_defautls << key + new_defaults << key end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index edb3d427d5..cd79468502 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/try' require 'active_support/core_ext/kernel/singleton_class' +require 'thread' module ActionView # = Action View Template @@ -122,6 +123,7 @@ module ActionView @virtual_path = details[:virtual_path] @updated_at = details[:updated_at] || Time.now @formats = Array(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f } + @compile_mutex = Mutex.new end # Returns if the underlying handler supports streaming. If so, @@ -223,18 +225,28 @@ module ActionView def compile!(view) #:nodoc: return if @compiled - if view.is_a?(ActionView::CompiledTemplates) - mod = ActionView::CompiledTemplates - else - mod = view.singleton_class - end + # Templates can be used concurrently in threaded environments + # so compilation and any instance variable modification must + # be synchronized + @compile_mutex.synchronize do + # Any thread holding this lock will be compiling the template needed + # by the threads waiting. So re-check the @compiled flag to avoid + # re-compilation + return if @compiled + + if view.is_a?(ActionView::CompiledTemplates) + mod = ActionView::CompiledTemplates + else + mod = view.singleton_class + end - compile(view, mod) + compile(view, mod) - # Just discard the source if we have a virtual path. This - # means we can get the template back. - @source = nil if @virtual_path - @compiled = true + # Just discard the source if we have a virtual path. This + # means we can get the template back. + @source = nil if @virtual_path + @compiled = true + end end # Among other things, this method is responsible for properly setting diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 9efe328d62..d5afef9086 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -223,6 +223,7 @@ end class ActionCachingTestController < CachingController rescue_from(Exception) { head 500 } + rescue_from(ActionController::UnknownFormat) { head :not_acceptable } if defined? ActiveRecord rescue_from(ActiveRecord::RecordNotFound) { head :not_found } end @@ -230,7 +231,7 @@ class ActionCachingTestController < CachingController # Eliminate uninitialized ivar warning before_filter { @title = nil } - caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour + caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| c.request.format && !c.request.format.json? }, :expires_in => 1.hour caches_action :show, :cache_path => 'http://test.host/custom/show' caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } caches_action :with_layout @@ -239,6 +240,7 @@ class ActionCachingTestController < CachingController caches_action :with_layout_proc_param, :layout => Proc.new { |c| c.params[:layout] } caches_action :record_not_found, :four_oh_four, :simple_runtime_error caches_action :streaming + caches_action :invalid layout 'talk_from_action' @@ -303,6 +305,14 @@ class ActionCachingTestController < CachingController def streaming render :text => "streaming", :stream => true end + + def invalid + @cache_this = MockTime.now.to_f.to_s + + respond_to do |format| + format.json{ render :json => @cache_this } + end + end end class MockTime < Time @@ -690,6 +700,25 @@ class ActionCacheTest < ActionController::TestCase assert fragment_exist?('hostname.com/action_caching_test/streaming') end + def test_invalid_format_returns_not_acceptable + get :invalid, :format => "json" + assert_response :success + cached_time = content_to_cache + assert_equal cached_time, @response.body + + assert fragment_exist?("hostname.com/action_caching_test/invalid.json") + + get :invalid, :format => "json" + assert_response :success + assert_equal cached_time, @response.body + + get :invalid, :format => "xml" + assert_response :not_acceptable + + get :invalid, :format => "\xC3\x83" + assert_response :not_acceptable + end + private def content_to_cache assigns(:cache_this) diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index fa1608b9df..5b05f77045 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -37,6 +37,14 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.last_parameters = nil end + def test_filtered_parameters + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu' } + assert_equal @request.filtered_parameters, { 'controller' => 'params_wrapper_test/users', 'action' => 'parse', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' } } + end + end + def test_derived_name_from_controller with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 9fc875014c..de1bff17eb 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -109,7 +109,7 @@ class ResourcesTest < ActionController::TestCase expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'} with_restful_routing :messages do - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get) end end @@ -660,15 +660,15 @@ class ResourcesTest < ActionController::TestCase options = { :controller => controller_name.to_s } collection_path = "/#{controller_name}" - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch) end - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put) end - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_recognizes(options.merge(:action => 'destroy'), :path => collection_path, :method => :delete) end end @@ -1353,7 +1353,7 @@ class ResourcesTest < ActionController::TestCase end def assert_not_recognizes(expected_options, path) - assert_raise ActionController::RoutingError, Assertion do + assert_raise Assertion do assert_recognizes(expected_options, path) end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index cd91064ab8..2f552c3a5a 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1037,6 +1037,16 @@ class RouteSetTest < ActiveSupport::TestCase end end + def test_route_error_with_missing_controller + set.draw do + get "/people" => "missing#index" + end + + assert_raise(ActionController::RoutingError) { + set.recognize_path("/people", :method => :get) + } + end + def test_recognize_with_encoded_id_and_regex set.draw do get 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/ diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 11c292d61a..6ff651ad52 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -35,6 +35,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest raise ActionController::InvalidAuthenticityToken when "/not_found_original_exception" raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new) + when "/bad_request" + raise ActionController::BadRequest else raise "puke!" end @@ -88,6 +90,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true} assert_response 405 assert_match(/ActionController::MethodNotAllowed/, body) + + get "/bad_request", {}, {'action_dispatch.show_exceptions' => true} + assert_response 400 + assert_match(/ActionController::BadRequest/, body) end test "does not show filtered parameters" do diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index d14f188e30..c3f009ab15 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -105,6 +105,17 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest ) end + test "ambiguous query string returns a bad request" do + with_routing do |set| + set.draw do + get ':action', :to => ::QueryStringParsingTest::TestController + end + + get "/parse", nil, "QUERY_STRING" => "foo[]=bar&foo[4]=bar" + assert_response :bad_request + end + end + private def assert_parses(expected, actual) with_routing do |set| diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index 4d24456ba6..80d5a13171 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -36,6 +36,22 @@ module ActionDispatch assert_equal s, Session.find(env) end + def test_keys + env = {} + s = Session.create(store, env, {}) + s['rails'] = 'ftw' + s['adequate'] = 'awesome' + assert_equal %w[rails adequate], s.keys + end + + def test_values + env = {} + s = Session.create(store, env, {}) + s['rails'] = 'ftw' + s['adequate'] = 'awesome' + assert_equal %w[ftw awesome], s.values + end + private def store Class.new { diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 568e220b15..e9b59f55a7 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -126,6 +126,17 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest assert_parses expected, query end + test "ambiguous params returns a bad request" do + with_routing do |set| + set.draw do + post ':action', :to => ::UrlEncodedParamsParsingTest::TestController + end + + post "/parse", "foo[]=bar&foo[4]=bar" + assert_response :bad_request + end + end + private def with_test_routing with_routing do |set| diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 94d0e09842..54fc1b208d 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -561,7 +561,7 @@ class RequestTest < ActiveSupport::TestCase begin request = stub_request(mock_rack_env) request.parameters - rescue TypeError + rescue ActionController::BadRequest # rack will raise a TypeError when parsing this query string end assert_equal({}, request.parameters) diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb index 517354ae58..aea4489852 100644 --- a/actionpack/test/dispatch/routing_assertions_test.rb +++ b/actionpack/test/dispatch/routing_assertions_test.rb @@ -54,21 +54,21 @@ class RoutingAssertionsTest < ActionController::TestCase end def test_assert_recognizes_with_hash_constraint - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'http://test.host/secure/articles') end assert_recognizes({ :controller => 'secure_articles', :action => 'index', :protocol => 'https://' }, 'https://test.host/secure/articles') end def test_assert_recognizes_with_block_constraint - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'http://test.host/block/articles') end assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'https://test.host/block/articles') end def test_assert_recognizes_with_query_constraint - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'false' }, '/query/articles', { :use_query => 'false' }) end assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'true' }, '/query/articles', { :use_query => 'true' }) @@ -87,14 +87,14 @@ class RoutingAssertionsTest < ActionController::TestCase end def test_assert_routing_with_hash_constraint - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_routing('http://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' }) end assert_routing('https://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index', :protocol => 'https://' }) end def test_assert_routing_with_block_constraint - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_routing('http://test.host/block/articles', { :controller => 'block_articles', :action => 'index' }) end assert_routing('https://test.host/block/articles', { :controller => 'block_articles', :action => 'index' }) @@ -107,7 +107,7 @@ class RoutingAssertionsTest < ActionController::TestCase end assert_routing('/artikel', :controller => 'articles', :action => 'index') - assert_raise(ActionController::RoutingError) do + assert_raise(Assertion) do assert_routing('/articles', { :controller => 'articles', :action => 'index' }) end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 1a8f40037f..00d09282ca 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2697,3 +2697,34 @@ class TestUrlConstraints < ActionDispatch::IntegrationTest assert_response :success end end + +class TestInvalidUrls < ActionDispatch::IntegrationTest + class FooController < ActionController::Base + def show + render :text => "foo#show" + end + end + + test "invalid UTF-8 encoding returns a 400 Bad Request" do + with_routing do |set| + set.draw do + get "/bar/:id", :to => redirect("/foo/show/%{id}") + get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show" + get "/foo(/:action(/:id))", :to => "test_invalid_urls/foo" + get "/:controller(/:action(/:id))" + end + + get "/%E2%EF%BF%BD%A6" + assert_response :bad_request + + get "/foo/%E2%EF%BF%BD%A6" + assert_response :bad_request + + get "/foo/show/%E2%EF%BF%BD%A6" + assert_response :bad_request + + get "/bar/%E2%EF%BF%BD%A6" + assert_response :bad_request + end + end +end
\ No newline at end of file diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 4ee1d61146..6047631ba3 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -55,6 +55,13 @@ class TestRequestTest < ActiveSupport::TestCase assert_cookies({"user_name" => "david"}, req.cookie_jar) end + test "does not complain when Rails.application is nil" do + Rails.stubs(:application).returns(nil) + req = ActionDispatch::TestRequest.new + + assert_equal false, req.env.empty? + end + private def assert_cookies(expected, cookie_jar) assert_equal(expected, cookie_jar.instance_variable_get("@cookies")) diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index bbb4cc5ef3..82f38b5309 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -214,3 +214,6 @@ class RenderJsonTestException < Exception return { :error => self.class.name, :message => self.to_s }.to_json end end + +class Car < Struct.new(:color) +end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 27cc3ad48a..c9b39ed18f 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -83,6 +83,8 @@ class FormHelperTest < ActionView::TestCase @post.tags << Tag.new @blog_post = Blog::Post.new("And his name will be forty and four.", 44) + + @car = Car.new("#000FFF") end Routes = ActionDispatch::Routing::RouteSet.new @@ -610,6 +612,17 @@ class FormHelperTest < ActionView::TestCase ) end + def test_color_field_with_valid_hex_color_string + expected = %{<input id="car_color" name="car[color]" type="color" value="#000fff" />} + assert_dom_equal(expected, color_field("car", "color")) + end + + def test_color_field_with_invalid_hex_color_string + expected = %{<input id="car_color" name="car[color]" type="color" value="#000000" />} + @car.color = "#1234TR" + assert_dom_equal(expected, color_field("car", "color")) + end + def test_search_field expected = %{<input id="contact_notes_query" name="contact[notes_query]" type="search" />} assert_dom_equal(expected, search_field("contact", "notes_query")) @@ -631,6 +644,15 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal(expected, date_field("post", "written_on")) end + def test_date_field_with_extra_attrs + expected = %{<input id="post_written_on" step="2" max="2010-08-15" min="2000-06-15" name="post[written_on]" type="date" value="2004-06-15" />} + @post.written_on = DateTime.new(2004, 6, 15) + min_value = DateTime.new(2000, 6, 15) + max_value = DateTime.new(2010, 8, 15) + step = 2 + assert_dom_equal(expected, date_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + end + def test_date_field_with_timewithzone_value previous_time_zone, Time.zone = Time.zone, 'UTC' expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />} @@ -657,6 +679,15 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal(expected, time_field("post", "written_on")) end + def test_time_field_with_extra_attrs + expected = %{<input id="post_written_on" step="60" max="10:25:00.000" min="20:45:30.000" name="post[written_on]" type="time" value="01:02:03.000" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + min_value = DateTime.new(2000, 6, 15, 20, 45, 30) + max_value = DateTime.new(2010, 8, 15, 10, 25, 00) + step = 60 + assert_dom_equal(expected, time_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + end + def test_time_field_with_timewithzone_value previous_time_zone, Time.zone = Time.zone, 'UTC' expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />} @@ -672,6 +703,146 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal(expected, time_field("post", "written_on")) end + def test_datetime_field + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T00:00:00.000+0000" />} + assert_dom_equal(expected, datetime_field("post", "written_on")) + end + + def test_datetime_field_with_datetime_value + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + assert_dom_equal(expected, datetime_field("post", "written_on")) + end + + def test_datetime_field_with_extra_attrs + expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00.000+0000" min="2000-06-15T20:45:30.000+0000" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + min_value = DateTime.new(2000, 6, 15, 20, 45, 30) + max_value = DateTime.new(2010, 8, 15, 10, 25, 00) + step = 60 + assert_dom_equal(expected, datetime_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + end + + def test_datetime_field_with_timewithzone_value + previous_time_zone, Time.zone = Time.zone, 'UTC' + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T15:30:45.000+0000" />} + @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + assert_dom_equal(expected, datetime_field("post", "written_on")) + ensure + Time.zone = previous_time_zone + end + + def test_datetime_field_with_nil_value + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" />} + @post.written_on = nil + assert_dom_equal(expected, datetime_field("post", "written_on")) + end + + def test_datetime_local_field + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T00:00:00" />} + assert_dom_equal(expected, datetime_local_field("post", "written_on")) + end + + def test_datetime_local_field_with_datetime_value + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + assert_dom_equal(expected, datetime_local_field("post", "written_on")) + end + + def test_datetime_local_field_with_extra_attrs + expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00" min="2000-06-15T20:45:30" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + min_value = DateTime.new(2000, 6, 15, 20, 45, 30) + max_value = DateTime.new(2010, 8, 15, 10, 25, 00) + step = 60 + assert_dom_equal(expected, datetime_local_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + end + + def test_datetime_local_field_with_timewithzone_value + previous_time_zone, Time.zone = Time.zone, 'UTC' + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T15:30:45" />} + @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + assert_dom_equal(expected, datetime_local_field("post", "written_on")) + ensure + Time.zone = previous_time_zone + end + + def test_datetime_local_field_with_nil_value + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" />} + @post.written_on = nil + assert_dom_equal(expected, datetime_local_field("post", "written_on")) + end + + def test_month_field + expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />} + assert_dom_equal(expected, month_field("post", "written_on")) + end + + def test_month_field_with_nil_value + expected = %{<input id="post_written_on" name="post[written_on]" type="month" />} + @post.written_on = nil + assert_dom_equal(expected, month_field("post", "written_on")) + end + + def test_month_field_with_datetime_value + expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + assert_dom_equal(expected, month_field("post", "written_on")) + end + + def test_month_field_with_extra_attrs + expected = %{<input id="post_written_on" step="2" max="2010-12" min="2000-02" name="post[written_on]" type="month" value="2004-06" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + min_value = DateTime.new(2000, 2, 13) + max_value = DateTime.new(2010, 12, 23) + step = 2 + assert_dom_equal(expected, month_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + end + + def test_month_field_with_timewithzone_value + previous_time_zone, Time.zone = Time.zone, 'UTC' + expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />} + @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + assert_dom_equal(expected, month_field("post", "written_on")) + ensure + Time.zone = previous_time_zone + end + + def test_week_field + expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />} + assert_dom_equal(expected, week_field("post", "written_on")) + end + + def test_week_field_with_nil_value + expected = %{<input id="post_written_on" name="post[written_on]" type="week" />} + @post.written_on = nil + assert_dom_equal(expected, week_field("post", "written_on")) + end + + def test_week_field_with_datetime_value + expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + assert_dom_equal(expected, week_field("post", "written_on")) + end + + def test_week_field_with_extra_attrs + expected = %{<input id="post_written_on" step="2" max="2010-W51" min="2000-W06" name="post[written_on]" type="week" value="2004-W24" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + min_value = DateTime.new(2000, 2, 13) + max_value = DateTime.new(2010, 12, 23) + step = 2 + assert_dom_equal(expected, week_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + end + + def test_week_field_with_timewithzone_value + previous_time_zone, Time.zone = Time.zone, 'UTC' + expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />} + @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + assert_dom_equal(expected, week_field("post", "written_on")) + ensure + Time.zone = previous_time_zone + end + def test_url_field expected = %{<input id="user_homepage" name="user[homepage]" type="url" />} assert_dom_equal(expected, url_field("user", "homepage")) diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 6574e13558..5d19e3274d 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -444,6 +444,11 @@ class FormTagHelperTest < ActionView::TestCase ) end + def test_color_field_tag + expected = %{<input id="car" name="car" type="color" />} + assert_dom_equal(expected, color_field_tag("car")) + end + def test_search_field_tag expected = %{<input id="query" name="query" type="search" />} assert_dom_equal(expected, search_field_tag("query")) @@ -464,6 +469,26 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal(expected, time_field_tag("cell")) end + def test_datetime_field_tag + expected = %{<input id="appointment" name="appointment" type="datetime" />} + assert_dom_equal(expected, datetime_field_tag("appointment")) + end + + def test_datetime_local_field_tag + expected = %{<input id="appointment" name="appointment" type="datetime-local" />} + assert_dom_equal(expected, datetime_local_field_tag("appointment")) + end + + def test_month_field_tag + expected = %{<input id="birthday" name="birthday" type="month" />} + assert_dom_equal(expected, month_field_tag("birthday")) + end + + def test_week_field_tag + expected = %{<input id="birthday" name="birthday" type="week" />} + assert_dom_equal(expected, week_field_tag("birthday")) + end + def test_url_field_tag expected = %{<input id="homepage" name="homepage" type="url" />} assert_dom_equal(expected, url_field_tag("homepage")) diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 97777ccff0..d496dbb35e 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -111,18 +111,28 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_translate_with_default_named_html translation = translate(:'translations.missing', :default => :'translations.hello_html') assert_equal '<a>Hello World</a>', translation - assert translation.html_safe? + assert_equal true, translation.html_safe? end def test_translate_with_two_defaults_named_html translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html']) assert_equal '<a>Hello World</a>', translation - assert translation.html_safe? + assert_equal true, translation.html_safe? end def test_translate_with_last_default_named_html translation = translate(:'translations.missing', :default => [:'translations.missing', :'translations.hello_html']) assert_equal '<a>Hello World</a>', translation - assert translation.html_safe? + assert_equal true, translation.html_safe? + end + + def test_translate_with_string_default + translation = translate(:'translations.missing', default: 'A Generic String') + assert_equal 'A Generic String', translation + end + + def test_translate_with_array_of_string_defaults + translation = translate(:'translations.missing', default: ['A Generic String', 'Second generic string']) + assert_equal 'A Generic String', translation end end diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb index 595b4018e9..c44c5d8968 100644 --- a/actionpack/test/ts_isolated.rb +++ b/actionpack/test/ts_isolated.rb @@ -9,7 +9,7 @@ class TestIsolated < ActiveSupport::TestCase define_method("test #{file}") do command = "#{ruby} -Ilib:test #{file}" result = silence_stderr { `#{command}` } - assert_block("#{command}\n#{result}") { $?.to_i.zero? } + assert $?.to_i.zero?, "#{command}\n#{result}" end end end diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index f2d004fb0a..66f324a1a1 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -8,6 +8,7 @@ Gem::Specification.new do |s| s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' s.required_ruby_version = '>= 1.9.3' + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index b78f1ff3f3..2b3e9ce134 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -172,7 +172,7 @@ module ActiveModel # <id type="integer">1</id> # <name>David</name> # <age type="integer">16</age> - # <created-at type="datetime">2011-01-30T22:29:23Z</created-at> + # <created-at type="dateTime">2011-01-30T22:29:23Z</created-at> # </user> # # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index 5fa227e0e0..7eb48abc3c 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -140,7 +140,7 @@ class XmlSerializationTest < ActiveModel::TestCase end test "should serialize datetime" do - assert_match %r{<created-at type=\"datetime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml + assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml end test "should serialize boolean" do diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index e8e5f4adfe..dca7f13fd2 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -8,6 +8,7 @@ Gem::Specification.new do |s| s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.' s.required_ruby_version = '>= 1.9.3' + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 6ad16bee2b..3ae7030caa 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -193,7 +193,8 @@ module ActiveRecord # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> # or a Proc that is called when a new value is assigned to the value object. The converter is # passed the single value that is used in the assignment and is only called if the new value is - # not an instance of <tt>:class_name</tt>. + # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter + # can return nil to skip the assignment. # # Option examples: # composed_of :temperature, :mapping => %w(reading celsius) @@ -241,16 +242,15 @@ module ActiveRecord def writer_method(name, class_name, mapping, allow_nil, converter) define_method("#{name}=") do |part| + klass = class_name.constantize + unless part.is_a?(klass) || converter.nil? || part.nil? + part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) + end + if part.nil? && allow_nil mapping.each { |pair| self[pair.first] = nil } @aggregation_cache[name] = nil else - unless part.is_a?(class_name.constantize) || converter.nil? - part = converter.respond_to?(:call) ? - converter.call(part) : - class_name.constantize.send(converter, part) - end - mapping.each { |pair| self[pair.first] = part.send(pair.last) } @aggregation_cache[name] = part.freeze end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 46c7fc71ac..be36c9695f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -55,19 +55,27 @@ module ActiveRecord # # == Options # - # There are two connection-pooling-related options that you can add to + # There are several connection-pooling-related options that you can add to # your database connection configuration: # # * +pool+: number indicating size of connection pool (default 5) - # * +wait_timeout+: number of seconds to block and wait for a connection + # * +checkout_timeout+: number of seconds to block and wait for a connection # before giving up and raising a timeout error (default 5 seconds). + # * +reaping_frequency+: frequency in seconds to periodically run the + # Reaper, which attempts to find and close dead connections, which can + # occur if a programmer forgets to close a connection at the end of a + # thread or a thread dies unexpectedly. (Default nil, which means don't + # run the Reaper). + # * +dead_connection_timeout+: number of seconds from last checkout + # after which the Reaper will consider a connection reapable. (default + # 5 seconds). class ConnectionPool # Every +frequency+ seconds, the reaper will call +reap+ on +pool+. # A reaper instantiated with a nil frequency will never reap the # connection pool. # # Configure the frequency by setting "reaping_frequency" in your - # database yaml file. + # database yaml file. class Reaper attr_reader :pool, :frequency @@ -89,7 +97,7 @@ module ActiveRecord include MonitorMixin - attr_accessor :automatic_reconnect, :timeout + attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout attr_reader :spec, :connections, :size, :reaper class Latch # :nodoc: @@ -121,7 +129,8 @@ module ActiveRecord # The cache of reserved connections mapped to threads @reserved_connections = {} - @timeout = spec.config[:wait_timeout] || 5 + @checkout_timeout = spec.config[:checkout_timeout] || 5 + @dead_connection_timeout = spec.config[:dead_connection_timeout] @reaper = Reaper.new self, spec.config[:reaping_frequency] @reaper.run @@ -139,14 +148,18 @@ module ActiveRecord # #connection can be called any number of times; the connection is # held in a hash keyed by the thread id. def connection - @reserved_connections[current_connection_id] ||= checkout + synchronize do + @reserved_connections[current_connection_id] ||= checkout + end end # Is there an open connection that is being used for the current thread? def active_connection? - @reserved_connections.fetch(current_connection_id) { - return false - }.in_use? + synchronize do + @reserved_connections.fetch(current_connection_id) { + return false + }.in_use? + end end # Signal that the thread is finished with the current connection. @@ -237,7 +250,7 @@ module ActiveRecord return checkout_and_verify(conn) if conn end - Timeout.timeout(@timeout, PoolFullError) { @latch.await } + Timeout.timeout(@checkout_timeout, PoolFullError) { @latch.await } end end @@ -275,7 +288,7 @@ module ActiveRecord # or a thread dies unexpectedly. def reap synchronize do - stale = Time.now - @timeout + stale = Time.now - @dead_connection_timeout connections.dup.each do |conn| remove conn if conn.in_use? && stale > conn.last_use && !conn.active? end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 14bc95abfe..15c3d7be36 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1336,11 +1336,15 @@ module ActiveRecord @connection.server_version end + # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html + FOREIGN_KEY_VIOLATION = "23503" + UNIQUE_VIOLATION = "23505" + def translate_exception(exception, message) - case exception.message - when /duplicate key value violates unique constraint/ + case exception.result.error_field(PGresult::PG_DIAG_SQLSTATE) + when UNIQUE_VIOLATION RecordNotUnique.new(message, exception) - when /violates foreign key constraint/ + when FOREIGN_KEY_VIOLATION InvalidForeignKey.new(message, exception) else super diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 2e60521638..b833af64fe 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -18,8 +18,8 @@ module ActiveRecord #:nodoc: # <id type="integer">1</id> # <approved type="boolean">false</approved> # <replies-count type="integer">0</replies-count> - # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time> - # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on> + # <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time> + # <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on> # <content>Have a nice day</content> # <author-email-address>david@loudthinking.com</author-email-address> # <parent-id></parent-id> diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index ce2ea85ef9..fdd82b489a 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/indifferent_access' + module ActiveRecord # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. # It's like a simple key/value store backed into your record when you don't care about being able to @@ -13,9 +15,6 @@ module ActiveRecord # You can set custom coder to encode/decode your serialized attributes to/from different formats. # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. # - # String keys should be used for direct access to virtual attributes because of most of the coders do not - # distinguish symbols and strings as keys. - # # Examples: # # class User < ActiveRecord::Base @@ -23,8 +22,12 @@ module ActiveRecord # end # # u = User.new(color: 'black', homepage: '37signals.com') - # u.color # Accessor stored attribute - # u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor + # u.color # Accessor stored attribute + # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor + # + # # There is no difference between strings and symbols for accessing custom attributes + # u.settings[:country] # => 'Denmark' + # u.settings['country'] # => 'Denmark' # # # Add additional accessors to an existing store through store_accessor # class SuperUser < User @@ -35,24 +38,38 @@ module ActiveRecord module ClassMethods def store(store_attribute, options = {}) - serialize store_attribute, options.fetch(:coder, Hash) + serialize store_attribute, options.fetch(:coder, ActiveSupport::HashWithIndifferentAccess) store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors end def store_accessor(store_attribute, *keys) keys.flatten.each do |key| define_method("#{key}=") do |value| - send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash) - send(store_attribute)[key.to_s] = value + initialize_store_attribute(store_attribute) + send(store_attribute)[key] = value send("#{store_attribute}_will_change!") end define_method(key) do - send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash) - send(store_attribute)[key.to_s] + initialize_store_attribute(store_attribute) + send(store_attribute)[key] end end end end + + private + def initialize_store_attribute(store_attribute) + case attribute = send(store_attribute) + when ActiveSupport::HashWithIndifferentAccess + # Already initialized. Do nothing. + when Hash + # Initialized as a Hash. Convert to indifferent access. + send :"#{store_attribute}=", attribute.with_indifferent_access + else + # Uninitialized. Set to an indifferent hash. + send :"#{store_attribute}=", ActiveSupport::HashWithIndifferentAccess.new + end + end end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 30e1035300..9cb9b4627b 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -329,7 +329,8 @@ module ActiveRecord @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 if @_start_transaction_state[:level] < 1 restore_state = remove_instance_variable(:@_start_transaction_state) - @attributes = @attributes.dup if @attributes.frozen? + was_frozen = @attributes.frozen? + @attributes = @attributes.dup if was_frozen @new_record = restore_state[:new_record] @destroyed = restore_state[:destroyed] if restore_state.has_key?(:id) @@ -338,6 +339,7 @@ module ActiveRecord @attributes.delete(self.class.primary_key) @attributes_cache.delete(self.class.primary_key) end + @attributes.freeze if was_frozen end end end diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 3e0e6dce2c..5bd8f76ba2 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -109,6 +109,24 @@ class AggregationsTest < ActiveRecord::TestCase assert_nil customers(:david).gps_location end + def test_nil_return_from_converter_is_respected_when_allow_nil_is_true + customers(:david).non_blank_gps_location = "" + customers(:david).save + customers(:david).reload + assert_nil customers(:david).non_blank_gps_location + end + + def test_nil_return_from_converter_results_in_failure_when_allow_nil_is_false + assert_raises(NoMethodError) do + customers(:barney).gps_location = "" + end + end + + def test_do_not_run_the_converter_when_nil_was_set + customers(:david).non_blank_gps_location = nil + assert_nil Customer.gps_conversion_was_run + end + def test_custom_constructor assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s assert_kind_of Fullname, customers(:barney).fullname diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 8dc9f761c2..bba7815d73 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -124,7 +124,7 @@ module ActiveRecord @pool.checkout @pool.checkout @pool.checkout - @pool.timeout = 0 + @pool.dead_connection_timeout = 0 connections = @pool.connections.dup @@ -137,7 +137,7 @@ module ActiveRecord @pool.checkout @pool.checkout @pool.checkout - @pool.timeout = 0 + @pool.dead_connection_timeout = 0 connections = @pool.connections.dup connections.each do |conn| diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index fba3006ebe..0a6354f5cc 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -17,7 +17,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase end def checkout_connections - ActiveRecord::Model.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3})) + ActiveRecord::Model.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.3})) @connections = [] @timed_out = 0 @@ -34,7 +34,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase # Will deadlock due to lack of Monitor timeouts in 1.9 def checkout_checkin_connections(pool_size, threads) - ActiveRecord::Model.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5})) + ActiveRecord::Model.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) @connection_count = 0 @timed_out = 0 threads.times do diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index 576ab60090..e53a27d5dd 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -64,7 +64,7 @@ module ActiveRecord spec.config[:reaping_frequency] = 0.0001 pool = ConnectionPool.new spec - pool.timeout = 0 + pool.dead_connection_timeout = 0 conn = pool.checkout count = pool.connections.length diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 4a56ae0d23..2dc8f0053b 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -133,7 +133,7 @@ class RelationTest < ActiveRecord::TestCase assert topics.loaded? end - def test_finiding_with_subquery + def test_finding_with_subquery relation = Topic.where(:approved => true) assert_equal relation.to_a, Topic.select('*').from(relation).to_a assert_equal relation.to_a, Topic.select('subquery.*').from(relation).to_a diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index e1d0f1f799..3a5d84df9f 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -41,6 +41,40 @@ class StoreTest < ActiveRecord::TestCase assert_equal false, @john.remember_login end + test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do + @john.json_data = HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy') + @john.height = 'low' + assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess) + assert_equal 'low', @john.json_data[:height] + assert_equal 'low', @john.json_data['height'] + assert_equal 'heavy', @john.json_data[:weight] + assert_equal 'heavy', @john.json_data['weight'] + end + + test "convert store attributes from Hash to HashWithIndifferentAccess saving the data and access attributes indifferently" do + @john.json_data = { :height => 'tall', 'weight' => 'heavy' } + assert_equal true, @john.json_data.instance_of?(Hash) + assert_equal 'tall', @john.json_data[:height] + assert_equal nil, @john.json_data['height'] + assert_equal nil, @john.json_data[:weight] + assert_equal 'heavy', @john.json_data['weight'] + @john.height = 'low' + assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess) + assert_equal 'low', @john.json_data[:height] + assert_equal 'low', @john.json_data['height'] + assert_equal 'heavy', @john.json_data[:weight] + assert_equal 'heavy', @john.json_data['weight'] + end + + test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do + @john.json_data = "somedata" + @john.height = 'low' + assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess) + assert_equal 'low', @john.json_data[:height] + assert_equal 'low', @john.json_data['height'] + assert_equal false, @john.json_data.delete_if { |k, v| k == 'height' }.any? + end + test "reading store attributes through accessors encoded with JSON" do assert_equal 'tall', @john.height assert_nil @john.weight diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 203dd054f1..a9ccd00fac 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -362,6 +362,16 @@ class TransactionTest < ActiveRecord::TestCase end end + def test_rollback_when_saving_a_frozen_record + topic = Topic.new(:title => 'test') + topic.freeze + e = assert_raise(RuntimeError) { topic.save } + assert_equal "can't modify frozen Hash", e.message + assert !topic.persisted?, 'not persisted' + assert_nil topic.id + assert topic.frozen?, 'not frozen' + end + def test_restore_active_record_state_for_all_records_in_a_transaction topic_1 = Topic.new(:title => 'test_1') topic_2 = Topic.new(:title => 'test_2') diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index 88751a72f9..12373333b0 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -92,7 +92,7 @@ class DefaultXmlSerializationTest < ActiveRecord::TestCase end def test_should_serialize_datetime - assert_match %r{<created-at type=\"datetime\">2006-08-01T00:00:00Z</created-at>}, @xml + assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @xml end def test_should_serialize_boolean @@ -109,7 +109,7 @@ class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase timezone, Time.zone = Time.zone, "Pacific Time (US & Canada)" toy = Toy.create(:name => 'Mickey', :updated_at => Time.utc(2006, 8, 1)) - assert_match %r{<updated-at type=\"datetime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml + assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml ensure Time.zone = timezone end @@ -118,7 +118,7 @@ class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase timezone, Time.zone = Time.zone, "Pacific Time (US & Canada)" toy = Toy.create(:name => 'Minnie', :updated_at => Time.utc(2006, 8, 1)).reload - assert_match %r{<updated-at type=\"datetime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml + assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml ensure Time.zone = timezone end @@ -152,7 +152,7 @@ class NilXmlSerializationTest < ActiveRecord::TestCase assert %r{<created-at (.*)></created-at>}.match(@xml) attributes = $1 assert_match %r{nil="true"}, attributes - assert_match %r{type="datetime"}, attributes + assert_match %r{type="dateTime"}, attributes end def test_should_serialize_boolean @@ -188,7 +188,7 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text - assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] + assert_equal "dateTime" , xml.elements["//written-on"].attributes['type'] assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text @@ -198,7 +198,7 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase if current_adapter?(:SybaseAdapter) assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text - assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] + assert_equal "dateTime" , xml.elements["//last-read"].attributes['type'] else # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) assert_equal "2004-04-15", xml.elements["//last-read"].text @@ -211,7 +211,7 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase assert_equal "boolean" , xml.elements["//approved"].attributes['type'] assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text - assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] + assert_equal "dateTime" , xml.elements["//bonus-time"].attributes['type'] end end diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb index 777f6b5ba0..7e8e82542f 100644 --- a/activerecord/test/models/customer.rb +++ b/activerecord/test/models/customer.rb @@ -1,7 +1,11 @@ class Customer < ActiveRecord::Base + cattr_accessor :gps_conversion_was_run + composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money } composed_of :gps_location, :allow_nil => true + composed_of :non_blank_gps_location, :class_name => "GpsLocation", :allow_nil => true, :mapping => %w(gps_location gps_location), + :converter => lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)} composed_of :fullname, :mapping => %w(name to_s), :constructor => Proc.new { |name| Fullname.parse(name) }, :converter => :parse end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 636aca2b8f..0171742347 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,17 @@ ## Rails 4.0.0 (unreleased) ## +* Add `Hash#transform_keys` and `Hash#transform_keys!`. *Mark McSpadden* + +* Changed xml type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri* + +* Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov* + +* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White* + +* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a +Hash+ instance into strings *Lucas Húngaro* + +* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a +Hash+ instance into symbols *Lucas Húngaro* + * `Object#try` can't call private methods. *Vasiliy Ermolovich* * `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 2c874e932e..30221f2401 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -8,6 +8,7 @@ Gem::Specification.new do |s| s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.' s.required_ruby_version = '>= 1.9.3' + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 8f018dcbc6..56d6676961 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -25,6 +25,7 @@ require 'securerandom' require "active_support/dependencies/autoload" require "active_support/version" require "active_support/logger" +require "active_support/lazy_load_hooks" module ActiveSupport extend ActiveSupport::Autoload diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 89bdb741d0..5be63af342 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -81,7 +81,8 @@ module ActiveSupport if File.exist?(file_name) File.open(file_name) { |f| Marshal.load(f) } end - rescue + rescue => e + logger.error("FileStoreError (#{e}): #{e.message}") if logger nil end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index c64685a694..7b6f8ab0a1 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -65,10 +65,12 @@ class Class # To opt out of the instance writer method, pass :instance_writer => false. # # object.setting = false # => NoMethodError + # + # To opt out of both instance methods, pass :instance_accessor => false. def class_attribute(*attrs) options = attrs.extract_options! - instance_reader = options.fetch(:instance_reader, true) - instance_writer = options.fetch(:instance_writer, true) + instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 469dc41f2d..43ba05a256 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -57,8 +57,8 @@ class Hash # "TrueClass" => "boolean", # "FalseClass" => "boolean", # "Date" => "date", - # "DateTime" => "datetime", - # "Time" => "datetime" + # "DateTime" => "dateTime", + # "Time" => "dateTime" # } # # By default the root node is "hash", but that's configurable via the <tt>:root</tt> option. diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index be4d611ce7..5eb861934d 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,46 +1,53 @@ class Hash - # Return a new hash with all keys converted to strings. + # Return a new hash with all keys converted using the block operation. # - # { :name => 'Rob', :years => '28' }.stringify_keys - # #=> { "name" => "Rob", "years" => "28" } - def stringify_keys + # { :name => 'Rob', :years => '28' }.transform_keys{ |key| key.to_s.upcase } + # # => { "NAME" => "Rob", "YEARS" => "28" } + def transform_keys result = {} keys.each do |key| - result[key.to_s] = self[key] + result[yield(key)] = self[key] end result end - # Destructively convert all keys to strings. Same as - # +stringify_keys+, but modifies +self+. - def stringify_keys! + # Destructively convert all keys using the block operations. + # Same as transform_keys but modifies +self+ + def transform_keys! keys.each do |key| - self[key.to_s] = delete(key) + self[yield(key)] = delete(key) end self end + # Return a new hash with all keys converted to strings. + # + # { :name => 'Rob', :years => '28' }.stringify_keys + # #=> { "name" => "Rob", "years" => "28" } + def stringify_keys + transform_keys{ |key| key.to_s } + end + + # Destructively convert all keys to strings. Same as + # +stringify_keys+, but modifies +self+. + def stringify_keys! + transform_keys!{ |key| key.to_s } + end + # Return a new hash with all keys converted to symbols, as long as # they respond to +to_sym+. # # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys # #=> { :name => "Rob", :years => "28" } def symbolize_keys - result = {} - keys.each do |key| - result[(key.to_sym rescue key)] = self[key] - end - result + transform_keys{ |key| key.to_sym rescue key } end alias_method :to_options, :symbolize_keys # Destructively convert all keys to symbols, as long as they respond # to +to_sym+. Same as +symbolize_keys+, but modifies +self+. def symbolize_keys! - keys.each do |key| - self[(key.to_sym rescue key)] = delete(key) - end - self + transform_keys!{ |key| key.to_sym rescue key } end alias_method :to_options!, :symbolize_keys! @@ -57,4 +64,48 @@ class Hash raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k) end end + + # Return a new hash with all keys converted to strings. + # This includes the keys from the root hash and from all + # nested hashes. + def deep_stringify_keys + result = {} + each do |key, value| + result[key.to_s] = value.is_a?(Hash) ? value.deep_stringify_keys : value + end + result + end + + # Destructively convert all keys to strings. + # This includes the keys from the root hash and from all + # nested hashes. + def deep_stringify_keys! + keys.each do |key| + val = delete(key) + self[key.to_s] = val.is_a?(Hash) ? val.deep_stringify_keys! : val + end + self + end + + # Destructively convert all keys to symbols, as long as they respond + # to +to_sym+. This includes the keys from the root hash and from all + # nested hashes. + def deep_symbolize_keys! + keys.each do |key| + val = delete(key) + self[(key.to_sym rescue key)] = val.is_a?(Hash) ? val.deep_stringify_keys! : val + end + self + end + + # Return a new hash with all keys converted to symbols, as long as + # they respond to +to_sym+. This includes the keys from the root hash + # and from all nested hashes. + def deep_symbolize_keys + result = {} + each do |key, value| + result[(key.to_sym rescue key)] = value.is_a?(Hash) ? value.deep_symbolize_keys : value + end + result + end end diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 48eb546a7d..30c835f5cd 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -7,6 +7,10 @@ class Object # # If try is called without a method to call, it will yield any given block with the object. # + # Please also note that +try+ is defined on +Object+, therefore it won't work with + # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will + # delegate +try+ to target instead of calling it on delegator itself. + # # ==== Examples # # Without +try+ diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index a1626ebeba..4045db3232 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -1,5 +1,4 @@ require "active_support/inflector/methods" -require "active_support/lazy_load_hooks" module ActiveSupport module Autoload diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 91459f3e5b..6e1c0da991 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -141,9 +141,13 @@ module ActiveSupport end def stringify_keys!; self end + def deep_stringify_keys!; self end def stringify_keys; dup end + def deep_stringify_keys; dup end undef :symbolize_keys! + undef :deep_symbolize_keys! def symbolize_keys; to_hash.symbolize_keys end + def deep_symbolize_keys; to_hash.deep_symbolize_keys end def to_options!; self end # Convert to a Hash with String keys. diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 48296841aa..2acc6ddee5 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -198,12 +198,29 @@ module ActiveSupport # # NameError is raised when the name is not in CamelCase or the constant is # unknown. - def constantize(camel_cased_word) #:nodoc: + def constantize(camel_cased_word) names = camel_cased_word.split('::') names.shift if names.empty? || names.first.empty? names.inject(Object) do |constant, name| - constant.const_get(name, false) + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check it it's owned + # directly before we reach Object or the end of ancestors. + constant = constant.ancestors.inject do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end end end diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 677e9910bb..88e18f6fff 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -39,8 +39,8 @@ module ActiveSupport "TrueClass" => "boolean", "FalseClass" => "boolean", "Date" => "date", - "DateTime" => "datetime", - "Time" => "datetime", + "DateTime" => "dateTime", + "Time" => "dateTime", "Array" => "array", "Hash" => "hash" } unless defined?(TYPE_NAMES) @@ -48,7 +48,7 @@ module ActiveSupport FORMATTING = { "symbol" => Proc.new { |symbol| symbol.to_s }, "date" => Proc.new { |date| date.to_s(:db) }, - "datetime" => Proc.new { |time| time.xmlschema }, + "dateTime" => Proc.new { |time| time.xmlschema }, "binary" => Proc.new { |binary| ::Base64.encode64(binary) }, "yaml" => Proc.new { |yaml| yaml.to_yaml } } unless defined?(FORMATTING) @@ -111,6 +111,7 @@ module ActiveSupport type_name ||= TYPE_NAMES[value.class.name] type_name ||= value.class.name if value && !value.respond_to?(:to_str) type_name = type_name.to_s if type_name + type_name = "dateTime" if type_name == "datetime" key = rename_key(key.to_s, options) @@ -145,7 +146,7 @@ module ActiveSupport "#{left}#{middle.tr('_ ', '--')}#{right}" end - # TODO: Add support for other encodings + # TODO: Add support for other encodings def _parse_binary(bin, entity) #:nodoc: case entity['encoding'] when 'base64' diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index d62b782e2d..a75db47be8 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -553,6 +553,9 @@ class FileStoreTest < ActiveSupport::TestCase @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), :expires_in => 60) + + @buffer = StringIO.new + @cache.logger = ActiveSupport::Logger.new(@buffer) end def teardown @@ -612,6 +615,12 @@ class FileStoreTest < ActiveSupport::TestCase ActiveSupport::Cache::FileStore.new('/test/cache/directory').delete_matched(/does_not_exist/) end end + + def test_log_exception_when_cache_read_fails + File.expects(:exist?).raises(StandardError, "failed") + @cache.send(:read_entry, "winston", {}) + assert_present @buffer.string + end end class MemoryStoreTest < ActiveSupport::TestCase diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index 135f894056..ec05213409 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -1,16 +1,41 @@ module Ace module Base class Case + class Dice + end + end + class Fase < Case + end + end + class Gas + include Base + end +end + +class Object + module AddtlGlobalConstants + class Case + class Dice + end end end + include AddtlGlobalConstants end module ConstantizeTestCases def run_constantize_tests_on assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } + assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") } + assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") } + assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") } + assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") } + assert_nothing_raised { assert_equal Object, yield("") } + assert_nothing_raised { assert_equal Object, yield("::") } assert_raise(NameError) { yield("UnknownClass") } assert_raise(NameError) { yield("UnknownClass::Ace") } assert_raise(NameError) { yield("UnknownClass::Ace::Base") } @@ -18,13 +43,23 @@ module ConstantizeTestCases assert_raise(NameError) { yield("InvalidClass\n") } assert_raise(NameError) { yield("Ace::ConstantizeTestCases") } assert_raise(NameError) { yield("Ace::Base::ConstantizeTestCases") } + assert_raise(NameError) { yield("Ace::Gas::Base") } + assert_raise(NameError) { yield("Ace::Gas::ConstantizeTestCases") } end def run_safe_constantize_tests_on assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } + assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") } + assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") } + assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") } + assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") } + assert_nothing_raised { assert_equal Object, yield("") } + assert_nothing_raised { assert_equal Object, yield("::") } assert_nothing_raised { assert_equal nil, yield("UnknownClass") } assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace") } assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace::Base") } @@ -33,6 +68,8 @@ module ConstantizeTestCases assert_nothing_raised { assert_equal nil, yield("blargle") } assert_nothing_raised { assert_equal nil, yield("Ace::ConstantizeTestCases") } assert_nothing_raised { assert_equal nil, yield("Ace::Base::ConstantizeTestCases") } + assert_nothing_raised { assert_equal nil, yield("Ace::Gas::Base") } + assert_nothing_raised { assert_equal nil, yield("Ace::Gas::ConstantizeTestCases") } assert_nothing_raised { assert_equal nil, yield("#<Class:0x7b8b718b>::Nested_1") } end end diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index e290a6e012..1c3ba8a7a0 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -66,6 +66,13 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_raise(NoMethodError) { object.setting? } end + test 'disabling both instance writer and reader' do + object = Class.new { class_attribute :setting, :instance_accessor => false }.new + assert_raise(NoMethodError) { object.setting } + assert_raise(NoMethodError) { object.setting? } + assert_raise(NoMethodError) { object.setting = 'boom' } + end + test 'works well with singleton classes' do object = @klass.new object.singleton_class.setting = 'foo' diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 8239054117..e68733db8b 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -24,56 +24,114 @@ class HashExtTest < ActiveSupport::TestCase def setup @strings = { 'a' => 1, 'b' => 2 } + @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } } @symbols = { :a => 1, :b => 2 } + @nested_symbols = { :a => { :b => { :c => 3 } } } @mixed = { :a => 1, 'b' => 2 } + @nested_mixed = { 'a' => { :b => { 'c' => 3 } } } @fixnums = { 0 => 1, 1 => 2 } + @nested_fixnums = { 0 => { 1 => { 2 => 3} } } @illegal_symbols = { [] => 3 } + @upcase_strings = { 'A' => 1, 'B' => 2 } + @nested_illegal_symbols = { [] => { [] => 3} } end def test_methods h = {} + assert_respond_to h, :transform_keys + assert_respond_to h, :transform_keys! assert_respond_to h, :symbolize_keys assert_respond_to h, :symbolize_keys! + assert_respond_to h, :deep_symbolize_keys + assert_respond_to h, :deep_symbolize_keys! assert_respond_to h, :stringify_keys assert_respond_to h, :stringify_keys! + assert_respond_to h, :deep_stringify_keys + assert_respond_to h, :deep_stringify_keys! assert_respond_to h, :to_options assert_respond_to h, :to_options! end + def test_transform_keys + assert_equal @upcase_strings, @strings.transform_keys{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @symbols.transform_keys{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase } + end + + def test_transform_keys! + assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase } + end + def test_symbolize_keys assert_equal @symbols, @symbols.symbolize_keys assert_equal @symbols, @strings.symbolize_keys assert_equal @symbols, @mixed.symbolize_keys end + def test_deep_symbolize_keys + assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys + assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys + assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys + end + def test_symbolize_keys! assert_equal @symbols, @symbols.dup.symbolize_keys! assert_equal @symbols, @strings.dup.symbolize_keys! assert_equal @symbols, @mixed.dup.symbolize_keys! end + def test_deep_symbolize_keys! + assert_equal @nested_symbols, @nested_symbols.dup.deep_symbolize_keys! + assert_equal @nested_symbols, @nested_strings.dup.deep_symbolize_keys! + assert_equal @nested_symbols, @nested_mixed.dup.deep_symbolize_keys! + end + def test_symbolize_keys_preserves_keys_that_cant_be_symbolized assert_equal @illegal_symbols, @illegal_symbols.symbolize_keys assert_equal @illegal_symbols, @illegal_symbols.dup.symbolize_keys! end + def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized + assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_symbolize_keys + assert_equal @nested_illegal_symbols, @nested_illegal_symbols.dup.deep_symbolize_keys! + end + def test_symbolize_keys_preserves_fixnum_keys assert_equal @fixnums, @fixnums.symbolize_keys assert_equal @fixnums, @fixnums.dup.symbolize_keys! end + def test_deep_symbolize_keys_preserves_fixnum_keys + assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys + assert_equal @nested_fixnums, @nested_fixnums.dup.deep_symbolize_keys! + end + def test_stringify_keys assert_equal @strings, @symbols.stringify_keys assert_equal @strings, @strings.stringify_keys assert_equal @strings, @mixed.stringify_keys end + def test_deep_stringify_keys + assert_equal @nested_strings, @nested_symbols.deep_stringify_keys + assert_equal @nested_strings, @nested_strings.deep_stringify_keys + assert_equal @nested_strings, @nested_mixed.deep_stringify_keys + end + def test_stringify_keys! assert_equal @strings, @symbols.dup.stringify_keys! assert_equal @strings, @strings.dup.stringify_keys! assert_equal @strings, @mixed.dup.stringify_keys! end + def test_deep_stringify_keys! + assert_equal @nested_strings, @nested_symbols.dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_strings.dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_mixed.dup.deep_stringify_keys! + end + def test_symbolize_keys_for_hash_with_indifferent_access assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys @@ -81,22 +139,46 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys end + def test_deep_symbolize_keys_for_hash_with_indifferent_access + assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys + end + + def test_symbolize_keys_bang_for_hash_with_indifferent_access assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! } assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! } assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! } end + def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access + assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.dup.deep_symbolize_keys! } + assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.dup.deep_symbolize_keys! } + assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.dup.deep_symbolize_keys! } + end + def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! } end + def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access + assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys + assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.dup.deep_symbolize_keys! } + end + def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! } end + def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access + assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys + assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.dup.deep_symbolize_keys! } + end + def test_stringify_keys_for_hash_with_indifferent_access assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys assert_equal @strings, @symbols.with_indifferent_access.stringify_keys @@ -104,6 +186,13 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @strings, @mixed.with_indifferent_access.stringify_keys end + def test_deep_stringify_keys_for_hash_with_indifferent_access + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys + end + def test_stringify_keys_bang_for_hash_with_indifferent_access assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys! assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys! @@ -111,6 +200,13 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys! end + def test_deep_stringify_keys_bang_for_hash_with_indifferent_access + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_strings.with_indifferent_access.dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_mixed.with_indifferent_access.dup.deep_stringify_keys! + end + def test_nested_under_indifferent_access foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"] @@ -297,6 +393,17 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, h[:first] end + def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash + h = HashWithIndifferentAccess.new + h[:first] = 1 + h = h.deep_stringify_keys + assert_equal 1, h['first'] + h = HashWithIndifferentAccess.new + h['first'] = 1 + h = h.deep_symbolize_keys + assert_equal 1, h[:first] + end + def test_to_options_on_indifferent_preserves_hash h = HashWithIndifferentAccess.new h['first'] = 1 @@ -672,8 +779,8 @@ class HashToXmlTest < ActiveSupport::TestCase :created_at => Time.utc(1999,2,2), :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)') }.to_xml(@xml_options) - assert_match %r{<created-at type=\"datetime\">1999-02-02T00:00:00Z</created-at>}, xml - assert_match %r{<local-created-at type=\"datetime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml + assert_match %r{<created-at type=\"dateTime\">1999-02-02T00:00:00Z</created-at>}, xml + assert_match %r{<local-created-at type=\"dateTime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml end def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb index 938bb4ee99..2c217157d3 100644 --- a/activesupport/test/ts_isolated.rb +++ b/activesupport/test/ts_isolated.rb @@ -10,7 +10,7 @@ class TestIsolated < ActiveSupport::TestCase define_method("test #{file}") do command = "#{ruby} -Ilib:test #{file}" result = silence_stderr { `#{command}` } - assert_block("#{command}\n#{result}") { $?.to_i.zero? } + assert $?.to_i.zero?, "#{command}\n#{result}" end end end diff --git a/guides/assets/images/belongs_to.png b/guides/assets/images/belongs_to.png Binary files differindex 44243edbca..43c963ffa8 100644 --- a/guides/assets/images/belongs_to.png +++ b/guides/assets/images/belongs_to.png diff --git a/guides/assets/images/book_icon.gif b/guides/assets/images/book_icon.gif Binary files differindex c81d5db520..efc5e06880 100644 --- a/guides/assets/images/book_icon.gif +++ b/guides/assets/images/book_icon.gif diff --git a/guides/assets/images/challenge.png b/guides/assets/images/challenge.png Binary files differindex d163748640..30be3d7028 100644 --- a/guides/assets/images/challenge.png +++ b/guides/assets/images/challenge.png diff --git a/guides/assets/images/chapters_icon.gif b/guides/assets/images/chapters_icon.gif Binary files differindex 06fb415f4a..a61c28c02d 100644 --- a/guides/assets/images/chapters_icon.gif +++ b/guides/assets/images/chapters_icon.gif diff --git a/guides/assets/images/check_bullet.gif b/guides/assets/images/check_bullet.gif Binary files differindex 1fcfeba250..bd54ef64c9 100644 --- a/guides/assets/images/check_bullet.gif +++ b/guides/assets/images/check_bullet.gif diff --git a/guides/assets/images/credits_pic_blank.gif b/guides/assets/images/credits_pic_blank.gif Binary files differindex f6f654fc65..a6b335d0c9 100644 --- a/guides/assets/images/credits_pic_blank.gif +++ b/guides/assets/images/credits_pic_blank.gif diff --git a/guides/assets/images/csrf.png b/guides/assets/images/csrf.png Binary files differindex ab73baafe8..a8123d47c3 100644 --- a/guides/assets/images/csrf.png +++ b/guides/assets/images/csrf.png diff --git a/guides/assets/images/customized_error_messages.png b/guides/assets/images/customized_error_messages.png Binary files differindex fa676991e3..fcf47b4be0 100644 --- a/guides/assets/images/customized_error_messages.png +++ b/guides/assets/images/customized_error_messages.png diff --git a/guides/assets/images/edge_badge.png b/guides/assets/images/edge_badge.png Binary files differindex cddd46c4b8..a35dc9f8ee 100644 --- a/guides/assets/images/edge_badge.png +++ b/guides/assets/images/edge_badge.png diff --git a/guides/assets/images/error_messages.png b/guides/assets/images/error_messages.png Binary files differindex 428892194a..1189e486d4 100644 --- a/guides/assets/images/error_messages.png +++ b/guides/assets/images/error_messages.png diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png Binary files differindex a26c09ef2d..1a13eddd91 100644 --- a/guides/assets/images/getting_started/confirm_dialog.png +++ b/guides/assets/images/getting_started/confirm_dialog.png diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png Binary files differindex badefe6ea6..6910e1647e 100644 --- a/guides/assets/images/getting_started/form_with_errors.png +++ b/guides/assets/images/getting_started/form_with_errors.png diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png Binary files differindex 6e58a13756..bf23cba231 100644 --- a/guides/assets/images/getting_started/index_action_with_edit_link.png +++ b/guides/assets/images/getting_started/index_action_with_edit_link.png diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png Binary files differindex dc9459032a..b573cb164c 100644 --- a/guides/assets/images/getting_started/new_post.png +++ b/guides/assets/images/getting_started/new_post.png diff --git a/guides/assets/images/getting_started/post_with_comments.png b/guides/assets/images/getting_started/post_with_comments.png Binary files differindex bd9b2e10f5..e13095ff8f 100644 --- a/guides/assets/images/getting_started/post_with_comments.png +++ b/guides/assets/images/getting_started/post_with_comments.png diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png Binary files differindex 92a39efd78..407ea2ea06 100644 --- a/guides/assets/images/getting_started/routing_error_no_controller.png +++ b/guides/assets/images/getting_started/routing_error_no_controller.png diff --git a/guides/assets/images/getting_started/routing_error_no_route_matches.png b/guides/assets/images/getting_started/routing_error_no_route_matches.png Binary files differindex bc768a94a2..d461807c5d 100644 --- a/guides/assets/images/getting_started/routing_error_no_route_matches.png +++ b/guides/assets/images/getting_started/routing_error_no_route_matches.png diff --git a/guides/assets/images/getting_started/show_action_for_posts.png b/guides/assets/images/getting_started/show_action_for_posts.png Binary files differindex 5c8c4d8e5e..9467df6a07 100644 --- a/guides/assets/images/getting_started/show_action_for_posts.png +++ b/guides/assets/images/getting_started/show_action_for_posts.png diff --git a/guides/assets/images/getting_started/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png Binary files differindex 9f070d59db..6860aaeca7 100644 --- a/guides/assets/images/getting_started/template_is_missing_posts_new.png +++ b/guides/assets/images/getting_started/template_is_missing_posts_new.png diff --git a/guides/assets/images/getting_started/undefined_method_post_path.png b/guides/assets/images/getting_started/undefined_method_post_path.png Binary files differindex f568bf315c..c29cb2f54f 100644 --- a/guides/assets/images/getting_started/undefined_method_post_path.png +++ b/guides/assets/images/getting_started/undefined_method_post_path.png diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png Binary files differindex 03d92dfb7d..1eca14b988 100644 --- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.png diff --git a/guides/assets/images/getting_started/unknown_action_new_for_posts.png b/guides/assets/images/getting_started/unknown_action_new_for_posts.png Binary files differindex b63883d922..fd72586573 100644 --- a/guides/assets/images/getting_started/unknown_action_new_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_new_for_posts.png diff --git a/guides/assets/images/grey_bullet.gif b/guides/assets/images/grey_bullet.gif Binary files differindex e75e8e93a1..3c08b1571c 100644 --- a/guides/assets/images/grey_bullet.gif +++ b/guides/assets/images/grey_bullet.gif diff --git a/guides/assets/images/habtm.png b/guides/assets/images/habtm.png Binary files differindex fea78b0b5c..b062bc73fe 100644 --- a/guides/assets/images/habtm.png +++ b/guides/assets/images/habtm.png diff --git a/guides/assets/images/has_many.png b/guides/assets/images/has_many.png Binary files differindex 6cff58460d..e7589e3b75 100644 --- a/guides/assets/images/has_many.png +++ b/guides/assets/images/has_many.png diff --git a/guides/assets/images/has_many_through.png b/guides/assets/images/has_many_through.png Binary files differindex 85d7599925..858c898dc1 100644 --- a/guides/assets/images/has_many_through.png +++ b/guides/assets/images/has_many_through.png diff --git a/guides/assets/images/has_one.png b/guides/assets/images/has_one.png Binary files differindex a70ddaaa86..93faa05b07 100644 --- a/guides/assets/images/has_one.png +++ b/guides/assets/images/has_one.png diff --git a/guides/assets/images/has_one_through.png b/guides/assets/images/has_one_through.png Binary files differindex 89a7617a30..07dac1a27d 100644 --- a/guides/assets/images/has_one_through.png +++ b/guides/assets/images/has_one_through.png diff --git a/guides/assets/images/header_backdrop.png b/guides/assets/images/header_backdrop.png Binary files differindex ff2982175e..72b030478f 100644 --- a/guides/assets/images/header_backdrop.png +++ b/guides/assets/images/header_backdrop.png diff --git a/guides/assets/images/i18n/demo_html_safe.png b/guides/assets/images/i18n/demo_html_safe.png Binary files differindex f881f60dac..9afa8ebec1 100644 --- a/guides/assets/images/i18n/demo_html_safe.png +++ b/guides/assets/images/i18n/demo_html_safe.png diff --git a/guides/assets/images/i18n/demo_localized_pirate.png b/guides/assets/images/i18n/demo_localized_pirate.png Binary files differindex 9134709573..bf8d0b558c 100644 --- a/guides/assets/images/i18n/demo_localized_pirate.png +++ b/guides/assets/images/i18n/demo_localized_pirate.png diff --git a/guides/assets/images/i18n/demo_translated_en.png b/guides/assets/images/i18n/demo_translated_en.png Binary files differindex ecdd878d38..e887bfa306 100644 --- a/guides/assets/images/i18n/demo_translated_en.png +++ b/guides/assets/images/i18n/demo_translated_en.png diff --git a/guides/assets/images/i18n/demo_translated_pirate.png b/guides/assets/images/i18n/demo_translated_pirate.png Binary files differindex 41c580923a..aa5618a865 100644 --- a/guides/assets/images/i18n/demo_translated_pirate.png +++ b/guides/assets/images/i18n/demo_translated_pirate.png diff --git a/guides/assets/images/i18n/demo_translation_missing.png b/guides/assets/images/i18n/demo_translation_missing.png Binary files differindex af9e2d0427..867aa7c42d 100644 --- a/guides/assets/images/i18n/demo_translation_missing.png +++ b/guides/assets/images/i18n/demo_translation_missing.png diff --git a/guides/assets/images/i18n/demo_untranslated.png b/guides/assets/images/i18n/demo_untranslated.png Binary files differindex 3603f43463..2ea6404822 100644 --- a/guides/assets/images/i18n/demo_untranslated.png +++ b/guides/assets/images/i18n/demo_untranslated.png diff --git a/guides/assets/images/icons/callouts/1.png b/guides/assets/images/icons/callouts/1.png Binary files differindex 7d473430b7..c5d02adcf4 100644 --- a/guides/assets/images/icons/callouts/1.png +++ b/guides/assets/images/icons/callouts/1.png diff --git a/guides/assets/images/icons/callouts/10.png b/guides/assets/images/icons/callouts/10.png Binary files differindex 997bbc8246..fe89f9ef83 100644 --- a/guides/assets/images/icons/callouts/10.png +++ b/guides/assets/images/icons/callouts/10.png diff --git a/guides/assets/images/icons/callouts/11.png b/guides/assets/images/icons/callouts/11.png Binary files differindex ce47dac3f5..9244a1ac4b 100644 --- a/guides/assets/images/icons/callouts/11.png +++ b/guides/assets/images/icons/callouts/11.png diff --git a/guides/assets/images/icons/callouts/12.png b/guides/assets/images/icons/callouts/12.png Binary files differindex 31daf4e2f2..ae56459f4c 100644 --- a/guides/assets/images/icons/callouts/12.png +++ b/guides/assets/images/icons/callouts/12.png diff --git a/guides/assets/images/icons/callouts/13.png b/guides/assets/images/icons/callouts/13.png Binary files differindex 14021a89c2..1181f9f892 100644 --- a/guides/assets/images/icons/callouts/13.png +++ b/guides/assets/images/icons/callouts/13.png diff --git a/guides/assets/images/icons/callouts/14.png b/guides/assets/images/icons/callouts/14.png Binary files differindex 64014b75fe..4274e6580a 100644 --- a/guides/assets/images/icons/callouts/14.png +++ b/guides/assets/images/icons/callouts/14.png diff --git a/guides/assets/images/icons/callouts/15.png b/guides/assets/images/icons/callouts/15.png Binary files differindex 0d65765fcf..39304de94f 100644 --- a/guides/assets/images/icons/callouts/15.png +++ b/guides/assets/images/icons/callouts/15.png diff --git a/guides/assets/images/icons/callouts/2.png b/guides/assets/images/icons/callouts/2.png Binary files differindex 5d09341b2f..8c57970ba9 100644 --- a/guides/assets/images/icons/callouts/2.png +++ b/guides/assets/images/icons/callouts/2.png diff --git a/guides/assets/images/icons/callouts/3.png b/guides/assets/images/icons/callouts/3.png Binary files differindex ef7b700471..57a33d15b4 100644 --- a/guides/assets/images/icons/callouts/3.png +++ b/guides/assets/images/icons/callouts/3.png diff --git a/guides/assets/images/icons/callouts/4.png b/guides/assets/images/icons/callouts/4.png Binary files differindex adb8364eb5..f061ab02b8 100644 --- a/guides/assets/images/icons/callouts/4.png +++ b/guides/assets/images/icons/callouts/4.png diff --git a/guides/assets/images/icons/callouts/5.png b/guides/assets/images/icons/callouts/5.png Binary files differindex 4d7eb46002..b4de02da11 100644 --- a/guides/assets/images/icons/callouts/5.png +++ b/guides/assets/images/icons/callouts/5.png diff --git a/guides/assets/images/icons/callouts/6.png b/guides/assets/images/icons/callouts/6.png Binary files differindex 0ba694af6c..0e055eec1e 100644 --- a/guides/assets/images/icons/callouts/6.png +++ b/guides/assets/images/icons/callouts/6.png diff --git a/guides/assets/images/icons/callouts/7.png b/guides/assets/images/icons/callouts/7.png Binary files differindex 472e96f8ac..5ead87d040 100644 --- a/guides/assets/images/icons/callouts/7.png +++ b/guides/assets/images/icons/callouts/7.png diff --git a/guides/assets/images/icons/callouts/8.png b/guides/assets/images/icons/callouts/8.png Binary files differindex 5e60973c21..cb99545eb6 100644 --- a/guides/assets/images/icons/callouts/8.png +++ b/guides/assets/images/icons/callouts/8.png diff --git a/guides/assets/images/icons/callouts/9.png b/guides/assets/images/icons/callouts/9.png Binary files differindex a0676d26cc..0ac03602f6 100644 --- a/guides/assets/images/icons/callouts/9.png +++ b/guides/assets/images/icons/callouts/9.png diff --git a/guides/assets/images/icons/caution.png b/guides/assets/images/icons/caution.png Binary files differindex cb9d5ea0df..031e19c776 100644 --- a/guides/assets/images/icons/caution.png +++ b/guides/assets/images/icons/caution.png diff --git a/guides/assets/images/icons/example.png b/guides/assets/images/icons/example.png Binary files differindex bba1c0010d..1b0e482059 100644 --- a/guides/assets/images/icons/example.png +++ b/guides/assets/images/icons/example.png diff --git a/guides/assets/images/icons/home.png b/guides/assets/images/icons/home.png Binary files differindex 37a5231bac..24149d6e78 100644 --- a/guides/assets/images/icons/home.png +++ b/guides/assets/images/icons/home.png diff --git a/guides/assets/images/icons/important.png b/guides/assets/images/icons/important.png Binary files differindex 1096c23295..dafcf0f59e 100644 --- a/guides/assets/images/icons/important.png +++ b/guides/assets/images/icons/important.png diff --git a/guides/assets/images/icons/next.png b/guides/assets/images/icons/next.png Binary files differindex 64e126bdda..355b329f5a 100644 --- a/guides/assets/images/icons/next.png +++ b/guides/assets/images/icons/next.png diff --git a/guides/assets/images/icons/note.png b/guides/assets/images/icons/note.png Binary files differindex 841820f7c4..08d35a6f5c 100644 --- a/guides/assets/images/icons/note.png +++ b/guides/assets/images/icons/note.png diff --git a/guides/assets/images/icons/prev.png b/guides/assets/images/icons/prev.png Binary files differindex 3e8f12fe24..ea564c865e 100644 --- a/guides/assets/images/icons/prev.png +++ b/guides/assets/images/icons/prev.png diff --git a/guides/assets/images/icons/tip.png b/guides/assets/images/icons/tip.png Binary files differindex a3a029d898..d834e6d1bb 100644 --- a/guides/assets/images/icons/tip.png +++ b/guides/assets/images/icons/tip.png diff --git a/guides/assets/images/icons/up.png b/guides/assets/images/icons/up.png Binary files differindex 2db1ce62fa..379f0045af 100644 --- a/guides/assets/images/icons/up.png +++ b/guides/assets/images/icons/up.png diff --git a/guides/assets/images/icons/warning.png b/guides/assets/images/icons/warning.png Binary files differindex 0b0c419df2..72a8a5d873 100644 --- a/guides/assets/images/icons/warning.png +++ b/guides/assets/images/icons/warning.png diff --git a/guides/assets/images/nav_arrow.gif b/guides/assets/images/nav_arrow.gif Binary files differindex c4f57658d7..ff081819ad 100644 --- a/guides/assets/images/nav_arrow.gif +++ b/guides/assets/images/nav_arrow.gif diff --git a/guides/assets/images/oscardelben.jpg b/guides/assets/images/oscardelben.jpg Binary files differnew file mode 100644 index 0000000000..9f3f67c2c7 --- /dev/null +++ b/guides/assets/images/oscardelben.jpg diff --git a/guides/assets/images/polymorphic.png b/guides/assets/images/polymorphic.png Binary files differindex ff2fd9f76d..a3cbc4502a 100644 --- a/guides/assets/images/polymorphic.png +++ b/guides/assets/images/polymorphic.png diff --git a/guides/assets/images/rails_guides_logo.gif b/guides/assets/images/rails_guides_logo.gif Binary files differindex a24683a34e..9b0ad5af28 100644 --- a/guides/assets/images/rails_guides_logo.gif +++ b/guides/assets/images/rails_guides_logo.gif diff --git a/guides/assets/images/rails_welcome.png b/guides/assets/images/rails_welcome.png Binary files differindex f2aa210d19..8ad2d351de 100644 --- a/guides/assets/images/rails_welcome.png +++ b/guides/assets/images/rails_welcome.png diff --git a/guides/assets/images/session_fixation.png b/guides/assets/images/session_fixation.png Binary files differindex 6b084508db..ac3ab01614 100644 --- a/guides/assets/images/session_fixation.png +++ b/guides/assets/images/session_fixation.png diff --git a/guides/assets/images/tab_grey.gif b/guides/assets/images/tab_grey.gif Binary files differindex e9680b7136..995adb76cf 100644 --- a/guides/assets/images/tab_grey.gif +++ b/guides/assets/images/tab_grey.gif diff --git a/guides/assets/images/tab_info.gif b/guides/assets/images/tab_info.gif Binary files differindex 458fea9a61..e9dd164f18 100644 --- a/guides/assets/images/tab_info.gif +++ b/guides/assets/images/tab_info.gif diff --git a/guides/assets/images/tab_note.gif b/guides/assets/images/tab_note.gif Binary files differindex 1d5c171ed6..f9b546c6f8 100644 --- a/guides/assets/images/tab_note.gif +++ b/guides/assets/images/tab_note.gif diff --git a/guides/assets/images/tab_red.gif b/guides/assets/images/tab_red.gif Binary files differindex daf140b5a8..0613093ddc 100644 --- a/guides/assets/images/tab_red.gif +++ b/guides/assets/images/tab_red.gif diff --git a/guides/assets/images/tab_yellow.gif b/guides/assets/images/tab_yellow.gif Binary files differindex dc961c99dd..39a3c2dc6a 100644 --- a/guides/assets/images/tab_yellow.gif +++ b/guides/assets/images/tab_yellow.gif diff --git a/guides/assets/images/tab_yellow.png b/guides/assets/images/tab_yellow.png Binary files differindex cceea6581f..3ab1c56c4d 100644 --- a/guides/assets/images/tab_yellow.png +++ b/guides/assets/images/tab_yellow.png diff --git a/guides/assets/images/validation_error_messages.png b/guides/assets/images/validation_error_messages.png Binary files differindex 622d35da5d..30e4ca4a3d 100644 --- a/guides/assets/images/validation_error_messages.png +++ b/guides/assets/images/validation_error_messages.png diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile index 80faffa49c..e2118e8f61 100644 --- a/guides/source/active_support_core_extensions.textile +++ b/guides/source/active_support_core_extensions.textile @@ -2549,6 +2549,38 @@ There's also the bang variant +except!+ that removes keys in the very receiver. NOTE: Defined in +active_support/core_ext/hash/except.rb+. +h5. +transform_keys+ and +transform_keys!+ + +The method +transform_keys+ accepts a block and returns a hash that has applied the block operations to each of the keys in the receiver: + +<ruby> +{nil => nil, 1 => 1, :a => :a}.transform_keys{ |key| key.to_s.upcase } +# => {"" => nil, "A" => :a, "1" => 1} +</ruby> + +The result in case of collision is undefined: + +<ruby> +{"a" => 1, :a => 2}.transform_keys{ |key| key.to_s.upcase } +# => {"A" => 2}, in my test, can't rely on this result though +</ruby> + +This method may be useful for example to build specialized conversions. For instance +stringify_keys+ and +symbolize_keys+ use +transform_keys+ to perform their key conversions: + +<ruby> +def stringify_keys + transform_keys{ |key| key.to_s } +end +... +def symbolize_keys + transform_keys{ |key| key.to_sym rescue key } +end +</ruby> + +There's also the bang variant +transform_keys!+ that applies the block operations to keys in the very receiver. + +NOTE: Defined in +active_support/core_ext/hash/keys.rb+. + h5. +stringify_keys+ and +stringify_keys!+ The method +stringify_keys+ returns a hash that has a stringified version of the keys in the receiver. It does so by sending +to_s+ to them: @@ -2579,6 +2611,13 @@ The second line can safely access the "type" key, and let the user to pass eithe There's also the bang variant +stringify_keys!+ that stringifies keys in the very receiver. +Besides that, one can use +deep_stringify_keys+ and +deep_stringify_keys!+ to stringify all the keys in the given hash and all the hashes nested into it. An example of the result is: + +<ruby> +{nil => nil, 1 => 1, :nested => {:a => 3, 5 => 5}}.deep_stringify_keys +# => {""=>nil, "1"=>1, "nested"=>{"a"=>3, "5"=>5}} +</ruby> + NOTE: Defined in +active_support/core_ext/hash/keys.rb+. h5. +symbolize_keys+ and +symbolize_keys!+ @@ -2613,6 +2652,13 @@ The second line can safely access the +:params+ key, and let the user to pass ei There's also the bang variant +symbolize_keys!+ that symbolizes keys in the very receiver. +Besides that, one can use +deep_symbolize_keys+ and +deep_symbolize_keys!+ to symbolize all the keys in the given hash and all the hashes nested into it. An example of the result is: + +<ruby> +{nil => nil, 1 => 1, "nested" => {"a" => 3, 5 => 5}}.deep_symbolize_keys +# => {nil=>nil, 1=>1, :nested=>{:a=>3, 5=>5}} +</ruby> + NOTE: Defined in +active_support/core_ext/hash/keys.rb+. h5. +to_options+ and +to_options!+ diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile index df475a2359..a2254a550c 100644 --- a/guides/source/contributing_to_ruby_on_rails.textile +++ b/guides/source/contributing_to_ruby_on_rails.textile @@ -343,9 +343,39 @@ h4. Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to git: <shell> -$ git commit -a -m "Here is a commit message on what I changed in this commit" +$ git commit -a </shell> +At this point, your editor should be fired up and you can write a message for this commit. Well formatted and descriptive commit messages are extremely helpful for the others, especially when figuring out why given change was made, so please take the time to write it. + +Good commit message should be formatted according to the following example: + +<plain> +Short summary (ideally 50 characters or less) + +More detailed description, if necessary. It should be wrapped to 72 +characters. Try to be as descriptive as you can, even if you think that +the commit content is obvious, it may not be obvious to others. You +should add such description also if it's already present in bug tracker, +it should not be necessary to visit a webpage to check the history. + +Description can have multiple paragraps and you can use code examples +inside, just indent it with 4 spaces: + + class PostsController + def index + respond_with Post.limit(10) + end + end + +You can also add bullet points: + +- you can use dashes or asterisks + +- also, try to indent next line of a point for readability, if it's too + long to fit in 72 characters +</plain> + TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean. h4. Update Master diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index da6bd6acdf..04deec6a11 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -31,6 +31,10 @@ Ruby on Rails Guides: Credits Ryan Bigg works as a consultant at <a href="http://rubyx.com">RubyX</a> and has been working with Rails since 2006. He's co-authoring a book called <a href="http://manning.com/katz">Rails 3 in Action</a> and he's written many gems which can be seen on <a href="http://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>. <% end %> +<%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %> +Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">Github account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>. + <% end %> + <%= author('Frederick Cheung', 'fcheung') do %> Frederick Cheung is Chief Wizard at Texperts where he has been using Rails since 2006. He is based in Cambridge (UK) and when not consuming fine ales he blogs at <a href="http://www.spacevatican.org">spacevatican.org</a>. <% end %> diff --git a/guides/source/form_helpers.textile b/guides/source/form_helpers.textile index 033b33ec3b..8106de6f9d 100644 --- a/guides/source/form_helpers.textile +++ b/guides/source/form_helpers.textile @@ -150,7 +150,7 @@ NOTE: Always use labels for checkbox and radio buttons. They associate text with h4. Other Helpers of Interest -Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, URL fields and email fields: +Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, color fields, datetime fields, datetime-local fields, month fields, week fields, URL fields and email fields: <erb> <%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %> @@ -159,8 +159,13 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel <%= search_field(:user, :name) %> <%= telephone_field(:user, :phone) %> <%= date_field(:user, :born_on) %> +<%= datetime_field(:user, :meeting_time) %> +<%= datetime_local_field(:user, :graduation_day) %> +<%= month_field(:user, :birthday_month) %> +<%= week_field(:user, :birthday_week) %> <%= url_field(:user, :homepage) %> <%= email_field(:user, :address) %> +<%= color_field(:user, :favorite_color) %> <%= time_field(:task, :started_at) %> </erb> @@ -173,14 +178,19 @@ Output: <input id="user_name" name="user[name]" type="search" /> <input id="user_phone" name="user[phone]" type="tel" /> <input id="user_born_on" name="user[born_on]" type="date" /> +<input id="user_meeting_time" name="user[meeting_time]" type="datetime" /> +<input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" /> +<input id="user_birthday_month" name="user[birthday_month]" type="month" /> +<input id="user_birthday_week" name="user[birthday_week]" type="week" /> <input id="user_homepage" name="user[homepage]" type="url" /> <input id="user_address" name="user[address]" type="email" /> +<input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" /> <input id="task_started_at" name="task[started_at]" type="time" /> </html> Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript. -IMPORTANT: The search, telephone, date, time, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features. +IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local, month, week, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features. TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging. diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile index 2b2e65c813..02407a5fe8 100644 --- a/guides/source/upgrading_ruby_on_rails.textile +++ b/guides/source/upgrading_ruby_on_rails.textile @@ -42,6 +42,10 @@ h4(#active_model4_0). ActiveModel Rails 4.0 has changed how errors attach with the ConfirmationValidator. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>. +h4(#action_pack4_0). ActionPack + +Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>RoutingError</tt>. + h3. Upgrading from Rails 3.1 to Rails 3.2 If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2. diff --git a/rails.gemspec b/rails.gemspec index 8314036ad1..52f92f46c6 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -9,6 +9,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 1.9.3' s.required_rubygems_version = ">= 1.8.11" + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index c4edbae55b..5c52235318 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -75,8 +75,12 @@ module Rails def initialize super - @initialized = false - @reloaders = [] + @initialized = false + @reloaders = [] + @routes_reloader = nil + @env_config = nil + @ordered_railties = nil + @queue = nil end # This method is called just after an application inherits from Rails::Application, diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index cd6a03fe51..b95df3e545 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -22,7 +22,7 @@ module Rails options = {} OptionParser.new do |opt| - opt.banner = "Usage: console [environment] [options]" + opt.banner = "Usage: rails console [environment] [options]" opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v } opt.on("-e", "--environment=name", String, "Specifies the environment to run this console under (test/development/production).", diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index aaba47117f..cc7caffc3d 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -42,7 +42,7 @@ module Rails include_password = false options = {} OptionParser.new do |opt| - opt.banner = "Usage: dbconsole [environment] [options]" + opt.banner = "Usage: rails dbconsole [environment] [options]" opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| include_password = true end @@ -56,6 +56,11 @@ module Rails options['header'] = h end + opt.on("-h", "--help", "Show this help message.") do + puts opt + exit + end + opt.parse!(arguments) abort opt.to_s unless (0..1).include?(arguments.size) end @@ -96,7 +101,7 @@ module Rails args << "-#{options['mode']}" if options['mode'] args << "-header" if options['header'] - args << config['database'] + args << File.expand_path(config['database'], Rails.root) find_cmd_and_exec('sqlite3', *args) diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index 2802981e7a..77f1b15fb4 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -9,7 +9,7 @@ if ARGV.first.nil? end ARGV.clone.options do |opts| - opts.banner = "Usage: runner [options] ('Some.ruby(code)' or a filename)" + opts.banner = "Usage: rails runner [options] ('Some.ruby(code)' or a filename)" opts.separator "" diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 3d66019e5e..5fa7f043c6 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -39,25 +39,25 @@ module Rails end def insert_before(*args, &block) - @operations << [:insert_before, args, block] + @operations << [__method__, args, block] end alias :insert :insert_before def insert_after(*args, &block) - @operations << [:insert_after, args, block] + @operations << [__method__, args, block] end def swap(*args, &block) - @operations << [:swap, args, block] + @operations << [__method__, args, block] end def use(*args, &block) - @operations << [:use, args, block] + @operations << [__method__, args, block] end def delete(*args, &block) - @operations << [:delete, args, block] + @operations << [__method__, args, block] end def merge_into(other) #:nodoc: diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 47856c87c6..e0950c6929 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -340,6 +340,18 @@ module Rails autoload :Configuration, "rails/engine/configuration" autoload :Railties, "rails/engine/railties" + def initialize + @_all_autoload_paths = nil + @_all_load_paths = nil + @app = nil + @config = nil + @env_config = nil + @helpers = nil + @railties = nil + @routes = nil + super + end + def load_generators(app=self) initialize_generators railties.all { |r| r.load_generators(app) } @@ -615,14 +627,14 @@ module Rails end end - protected + protected def initialize_generators require "rails/generators" end def routes? - defined?(@routes) && @routes + @routes end def has_migrations? @@ -640,8 +652,7 @@ module Rails root = File.exist?("#{root_path}/#{flag}") ? root_path : default raise "Could not find root path for #{self}" unless root - RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? - Pathname.new(root).expand_path : Pathname.new(root).realpath + Pathname.new File.realpath root end def default_middleware_stack diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index d3b42021fc..e31df807a6 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -20,7 +20,7 @@ module Rails # Holds generators configuration: # # config.generators do |g| - # g.orm :datamapper, :migration => true + # g.orm :data_mapper, :migration => true # g.template_engine :haml # g.test_framework :rspec # end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 703501e6f0..c41acc7841 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -26,7 +26,7 @@ module Rails log :gemfile, message options.each do |option, value| - parts << ":#{option} => #{value.inspect}" + parts << "#{option}: #{value.inspect}" end in_root do diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb index 454327f765..0e51b9c568 100644 --- a/railties/lib/rails/generators/active_model.rb +++ b/railties/lib/rails/generators/active_model.rb @@ -11,7 +11,7 @@ module Rails # ActiveRecord::Generators::ActiveModel.find(Foo, "params[:id]") # # => "Foo.find(params[:id])" # - # Datamapper::Generators::ActiveModel.find(Foo, "params[:id]") + # DataMapper::Generators::ActiveModel.find(Foo, "params[:id]") # # => "Foo.get(params[:id])" # # On initialization, the ActiveModel accepts the instance name that will diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index e85d1b8fa2..63703176de 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -79,6 +79,10 @@ module Rails @class_path end + def namespaced_file_path + @namespaced_file_path ||= namespaced_class_path.join("/") + end + def namespaced_class_path @namespaced_class_path ||= begin namespace_path = namespace.name.split("::").map {|m| m.underscore } diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index 286e93c3cf..303e47877f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -1,6 +1,10 @@ <%= app_const %>.routes.draw do # The priority is based upon order of creation: # first created -> highest priority. + + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + # root :to => 'welcome#index' # Sample of regular route: # get 'products/:id' => 'catalog#view' @@ -46,9 +50,6 @@ # resources :products # end - # You can have the root of your site routed with "root" - # just remember to delete public/index.html. - # root :to => 'welcome#index' # See how all your routes lay out with "rake routes" end
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb index 52243f4a2f..ece6bbba3b 100644 --- a/railties/lib/rails/generators/rails/controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb @@ -1,3 +1,7 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_file_path %>/application_controller" +<% end -%> + <% module_namespacing do -%> class <%= class_name %>Controller < ApplicationController <% actions.each do |action| -%> diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 722e37e20b..7088367462 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -139,7 +139,7 @@ task :default => :test gemfile_in_app_path = File.join(rails_app_path, "Gemfile") if File.exist? gemfile_in_app_path - entry = "gem '#{name}', :path => '#{relative_path}'" + entry = "gem '#{name}', path: '#{relative_path}'" append_file gemfile_in_app_path, entry end end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index b95aea5f19..0294bde582 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -1,3 +1,7 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_file_path %>/application_controller" +<% end -%> + <% module_namespacing do -%> class <%= controller_class_name %>Controller < ApplicationController # GET <%= route_url %> diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 7067253279..2a39826c8d 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -7,6 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Tools for creating, working with, and running Rails applications.' s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.' s.required_ruby_version = '>= 1.9.3' + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index bf58bb3f74..b80244f1d2 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -45,10 +45,10 @@ module ApplicationTests test "generators set rails options" do with_bare_config do |c| - c.generators.orm = :datamapper + c.generators.orm = :data_mapper c.generators.test_framework = :rspec c.generators.helper = false - expected = { :rails => { :orm => :datamapper, :test_framework => :rspec, :helper => false } } + expected = { :rails => { :orm => :data_mapper, :test_framework => :rspec, :helper => false } } assert_equal(expected, c.generators.options) end end @@ -64,7 +64,7 @@ module ApplicationTests test "generators aliases, options, templates and fallbacks on initialization" do add_to_config <<-RUBY config.generators.rails :aliases => { :test_framework => "-w" } - config.generators.orm :datamapper + config.generators.orm :data_mapper config.generators.test_framework :rspec config.generators.fallbacks[:shoulda] = :test_unit config.generators.templates << "some/where" @@ -95,15 +95,15 @@ module ApplicationTests test "generators with hashes for options and aliases" do with_bare_config do |c| c.generators do |g| - g.orm :datamapper, :migration => false + g.orm :data_mapper, :migration => false g.plugin :aliases => { :generator => "-g" }, :generator => true end expected = { - :rails => { :orm => :datamapper }, + :rails => { :orm => :data_mapper }, :plugin => { :generator => true }, - :datamapper => { :migration => false } + :data_mapper => { :migration => false } } assert_equal expected, c.generators.options @@ -114,12 +114,12 @@ module ApplicationTests test "generators with string and hash for options should generate symbol keys" do with_bare_config do |c| c.generators do |g| - g.orm 'datamapper', :migration => false + g.orm 'data_mapper', :migration => false end expected = { - :rails => { :orm => :datamapper }, - :datamapper => { :migration => false } + :rails => { :orm => :data_mapper }, + :data_mapper => { :migration => false } } assert_equal expected, c.generators.options diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb index 85a7edfacd..6d0f5ca073 100644 --- a/railties/test/commands/dbconsole_test.rb +++ b/railties/test/commands/dbconsole_test.rb @@ -92,20 +92,25 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase end def test_sqlite3 - dbconsole.expects(:find_cmd_and_exec).with('sqlite3', 'db') - start(adapter: 'sqlite3', database: 'db') + dbconsole.expects(:find_cmd_and_exec).with('sqlite3', Rails.root.join('db.sqlite3').to_s) + start(adapter: 'sqlite3', database: 'db.sqlite3') assert !aborted end def test_sqlite3_mode - dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', 'db') - start({adapter: 'sqlite3', database: 'db'}, ['--mode', 'html']) + dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', Rails.root.join('db.sqlite3').to_s) + start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--mode', 'html']) assert !aborted end def test_sqlite3_header - dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', 'db') - start({adapter: 'sqlite3', database: 'db'}, ['--header']) + dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', Rails.root.join('db.sqlite3').to_s) + start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--header']) + end + + def test_sqlite3_db_absolute_path + dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '/tmp/db.sqlite3') + start(adapter: 'sqlite3', database: '/tmp/db.sqlite3') assert !aborted end @@ -127,6 +132,24 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase assert_match /Unknown command-line client for db/, output end + def test_print_help_short + stdout = capture(:stdout) do + start({}, ['-h']) + end + assert aborted + assert_equal '', output + assert_match /Usage:.*dbconsole/, stdout + end + + def test_print_help_long + stdout = capture(:stdout) do + start({}, ['--help']) + end + assert aborted + assert_equal '', output + assert_match /Usage:.*dbconsole/, stdout + end + private attr_reader :aborted, :output diff --git a/railties/test/configuration/middleware_stack_proxy_test.rb b/railties/test/configuration/middleware_stack_proxy_test.rb new file mode 100644 index 0000000000..5984c0b425 --- /dev/null +++ b/railties/test/configuration/middleware_stack_proxy_test.rb @@ -0,0 +1,59 @@ +require 'minitest/autorun' +require 'rails/configuration' +require 'active_support/test_case' + +module Rails + module Configuration + class MiddlewareStackProxyTest < ActiveSupport::TestCase + def setup + @stack = MiddlewareStackProxy.new + end + + def test_playback_insert_before + @stack.insert_before :foo + assert_playback :insert_before, :foo + end + + def test_playback_insert_after + @stack.insert_after :foo + assert_playback :insert_after, :foo + end + + def test_playback_swap + @stack.swap :foo + assert_playback :swap, :foo + end + + def test_playback_use + @stack.use :foo + assert_playback :use, :foo + end + + def test_playback_delete + @stack.delete :foo + assert_playback :delete, :foo + end + + def test_order + @stack.swap :foo + @stack.delete :foo + + mock = MiniTest::Mock.new + mock.expect :send, nil, [:swap, :foo] + mock.expect :send, nil, [:delete, :foo] + + @stack.merge_into mock + mock.verify + end + + private + + def assert_playback(msg_name, args) + mock = MiniTest::Mock.new + mock.expect :send, nil, [msg_name, args] + @stack.merge_into(mock) + mock.verify + end + end + end +end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index a8c8fcd5b7..bc086c5986 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -67,6 +67,14 @@ class ActionsTest < Rails::Generators::TestCase assert_file 'Gemfile', /^gem "rspec-rails"$/ end + def test_gem_should_include_options + run_generator + + action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1' + + assert_file 'Gemfile', /gem "rspec", github: "dchelimsky\/rspec", tag: "1\.2\.9\.rc1"/ + end + def test_gem_group_should_wrap_gems_in_a_group run_generator diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb index d6338bd3da..a2b94f2e50 100644 --- a/railties/test/generators/assets_generator_test.rb +++ b/railties/test/generators/assets_generator_test.rb @@ -1,7 +1,6 @@ require 'generators/generators_test_helper' require 'rails/generators/rails/assets/assets_generator' -# FIXME: Silence the 'Could not find task "using_coffee?"' message in tests due to the public stub class AssetsGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments %w(posts) diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index c495258343..09169ef2d2 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -20,8 +20,14 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase def test_namespaced_controller_skeleton_is_created run_generator - assert_file "app/controllers/test_app/account_controller.rb", /module TestApp/, / class AccountController < ApplicationController/ - assert_file "test/functional/test_app/account_controller_test.rb", /module TestApp/, / class AccountControllerTest/ + assert_file "app/controllers/test_app/account_controller.rb", + /require_dependency "test_app\/application_controller"/, + /module TestApp/, + / class AccountController < ApplicationController/ + + assert_file "test/functional/test_app/account_controller_test.rb", + /module TestApp/, + / class AccountControllerTest/ end def test_skipping_namespace @@ -227,9 +233,10 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase end # Controller - assert_file "app/controllers/test_app/product_lines_controller.rb" do |content| - assert_match(/module TestApp\n class ProductLinesController < ApplicationController/, content) - end + assert_file "app/controllers/test_app/product_lines_controller.rb", + /require_dependency "test_app\/application_controller"/, + /module TestApp/, + /class ProductLinesController < ApplicationController/ assert_file "test/functional/test_app/product_lines_controller_test.rb", /module TestApp\n class ProductLinesControllerTest < ActionController::TestCase/ diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 51374e5f3f..58740978aa 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -279,7 +279,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase run_generator [destination_root] - assert_file gemfile_path, /gem 'bukkits', :path => 'tmp\/bukkits'/ + assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/ ensure Object.send(:remove_const, 'APP_PATH') FileUtils.rm gemfile_path @@ -294,7 +294,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-gemfile-entry"] assert_file gemfile_path do |contents| - assert_no_match(/gem 'bukkits', :path => 'tmp\/bukkits'/, contents) + assert_no_match(/gem 'bukkits', path: 'tmp\/bukkits'/, contents) end ensure Object.send(:remove_const, 'APP_PATH') diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 55f72f532f..7a047ef93a 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -882,7 +882,7 @@ YAML module Bukkits class Engine < ::Rails::Engine config.generators do |g| - g.orm :datamapper + g.orm :data_mapper g.template_engine :haml g.test_framework :rspec end @@ -910,7 +910,7 @@ YAML assert_equal :test_unit, app_generators[:test_framework] generators = Bukkits::Engine.config.generators.options[:rails] - assert_equal :datamapper, generators[:orm] + assert_equal :data_mapper, generators[:orm] assert_equal :haml , generators[:template_engine] assert_equal :rspec , generators[:test_framework] end |