diff options
author | Alvaro Pereyra <alvaro@xendacentral.com> | 2012-05-28 02:29:46 -0500 |
---|---|---|
committer | Alvaro Pereyra <alvaro@xendacentral.com> | 2012-05-28 02:29:46 -0500 |
commit | 72973a30704894c808836d80a001208c1af39e7c (patch) | |
tree | ab84954fed3e67628bb0884b0e4b0376638bc5dc | |
parent | 011863673a353c334ddb2c93227dceadc5d7c3b6 (diff) | |
parent | 0ad2146ccf45b3a26924e729a92cd2ff98356413 (diff) | |
download | rails-72973a30704894c808836d80a001208c1af39e7c.tar.gz rails-72973a30704894c808836d80a001208c1af39e7c.tar.bz2 rails-72973a30704894c808836d80a001208c1af39e7c.zip |
Merge branch 'master' of github.com:lifo/docrails
320 files changed, 3195 insertions, 683 deletions
@@ -179,6 +179,7 @@ end # We publish a new version by tagging, and pushing a tag does not trigger # that webhook. Stable docs would be updated by any subsequent regular # push, but if you want that to happen right away just run this. +# desc 'Publishes docs, run this AFTER a new stable tag has been pushed' task :publish_docs do Net::HTTP.new('api.rubyonrails.org', 8080).start do |http| 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/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index d2ec48b1ab..3b38dbccc7 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -50,6 +50,7 @@ module ActionMailer # add_delivery_method :sendmail, Mail::Sendmail, # :location => '/usr/sbin/sendmail', # :arguments => '-i -t' + # def add_delivery_method(symbol, klass, default_options={}) class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings") send(:"#{symbol}_settings=", default_options) diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb index 835fc9b148..edcfb4233d 100644 --- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb +++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb @@ -7,6 +7,7 @@ class <%= class_name %> < ActionMailer::Base # with the following lookup: # # en.<%= file_path.tr("/",".") %>.<%= action %>.subject + # def <%= action %> @greeting = "Hi" diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index e625bbe7af..ba66b525bc 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,24 @@ ## Rails 4.0.0 (unreleased) ## +* `truncate` now always returns an escaped HTMl-safe string. The option `:escape` can be used as + false to not escape the result. + + *Li Ellis Gallardo + Rafael Mendonça França* + +* `truncate` now accepts a block to show extra content when the text is truncated. *Li Ellis Gallardo* + +* 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/base.rb b/actionpack/lib/abstract_controller/base.rb index 32ec7ced0f..9c3960961b 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -141,7 +141,7 @@ module AbstractController # # Notice that <tt>action_methods.include?("foo")</tt> may return # false and <tt>available_action?("foo")</tt> returns true because - # available action consider actions that are also available + # this method considers actions that are also available # through other means, for example, implicit render ones. # # ==== Parameters diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 529f920e6c..4e0672d590 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -90,6 +90,7 @@ module AbstractController # +symbols+, +strings+, +modules+ and blocks. # # helper(:three, BlindHelper) { def mice() 'mice' end } + # def helper(*args, &block) modules_for_helpers(args).each do |mod| add_template_helper(mod) 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/base.rb b/actionpack/lib/action_controller/base.rb index 90058245f5..71425cd542 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -167,6 +167,7 @@ module ActionController # redirect_to(:action => "elsewhere") and return if monkeys.nil? # render :action => "overthere" # won't be called if monkeys is nil # end + # class Base < Metal abstract! 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.rb b/actionpack/lib/action_controller/metal.rb index 720c0f2258..92433ab462 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -9,6 +9,7 @@ module ActionController # class PostsController < ApplicationController # use AuthenticationMiddleware, :except => [:index, :show] # end + # class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc: class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc: def initialize(klass, *args, &block) @@ -96,6 +97,7 @@ module ActionController # # You can refer to the modules included in <tt>ActionController::Base</tt> to see # other features you can bring into your metal controller. + # class Metal < AbstractController::Base abstract! 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/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 598bc6c5cb..86d061e3b7 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -47,6 +47,7 @@ module ActionController # # 23 Aug 11:30 | Carolina Railhawks Soccer Match # N/A | Carolina Railhaws Training Workshop + # module Helpers extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index d9fc777250..0b800c3c62 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -52,6 +52,7 @@ module ActionController #:nodoc: end # Clear all mime types in <tt>respond_to</tt>. + # def clear_respond_to self.mimes_for_respond_to = Hash.new.freeze 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_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 5aa3b2ca15..83407846dc 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -142,11 +142,13 @@ module ActionController #:nodoc: # Initializes a new responder an invoke the proper format. If the format is # not defined, call to_format. + # def self.call(*args) new(*args).respond end # Main entry point for responder responsible to dispatch to the proper format. + # def respond method = "to_#{format}" respond_to?(method) ? send(method) : to_format @@ -154,6 +156,7 @@ module ActionController #:nodoc: # HTML format does not render the resource, it always attempt to render a # template. + # def to_html default_render rescue ActionView::MissingTemplate => e @@ -168,6 +171,7 @@ module ActionController #:nodoc: # All other formats follow the procedure below. First we try to render a # template, if the template is not available, we verify if the resource # responds to :to_format and display it. + # def to_format if get? || !has_errors? || response_overridden? default_render @@ -205,12 +209,14 @@ module ActionController #:nodoc: end # Checks whether the resource responds to the current format or not. + # def resourceful? resource.respond_to?("to_#{format}") end # Returns the resource location by retrieving it from the options or # returning the resources array. + # def resource_location options[:location] || resources end @@ -219,6 +225,7 @@ module ActionController #:nodoc: # If a response block was given, use it, otherwise call render on # controller. + # def default_render if @default_response @default_response.call(options) @@ -243,6 +250,7 @@ module ActionController #:nodoc: # Results in: # # render :xml => @user, :status => :created + # def display(resource, given_options={}) controller.render given_options.merge!(options).merge!(format => resource) end @@ -252,12 +260,14 @@ module ActionController #:nodoc: end # Check whether the resource has errors. + # def has_errors? resource.respond_to?(:errors) && !resource.errors.empty? end # By default, render the <code>:edit</code> action for HTML requests with errors, unless # the verb was POST. + # def default_action @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol] end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 0c3caa9514..eeb37db2e7 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -194,6 +194,7 @@ module ActionController #:nodoc: # ==== Passenger # # To be described. + # module Streaming extend ActiveSupport::Concern diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index ca40ab9502..e31f3b823d 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -46,6 +46,7 @@ module ActionDispatch # GET /posts/5.xml | request.format => Mime::XML # GET /posts/5.xhtml | request.format => Mime::HTML # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first + # def format(view_path = []) formats.first end @@ -81,6 +82,7 @@ module ActionDispatch # Receives an array of mimes and return the first user sent mime that # matches the order array. + # def negotiate_mime(order) formats.each do |priority| if priority == Mime::ALL 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/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 23415dae54..2f6968eb2e 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -22,6 +22,7 @@ module ActionDispatch # is false. Callbacks may be registered even when it is not included in the # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt> # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually. + # class Reloader include ActiveSupport::Callbacks 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.rb b/actionpack/lib/action_dispatch/routing.rb index a2a6fb39dc..38a0270151 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -277,6 +277,7 @@ module ActionDispatch # rake routes # # Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>. + # module Routing autoload :Mapper, 'action_dispatch/routing/mapper' autoload :RouteSet, 'action_dispatch/routing/route_set' diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 79eee21619..e43e897783 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -897,6 +897,7 @@ module ActionDispatch # resources :articles, :id => /[^\/]+/ # # This allows any character other than a slash as part of your +:id+. + # module Resources # CANONICAL_ACTIONS holds all actions that does not need a prefix or # a path appended since they fit properly in their scope level. @@ -1317,7 +1318,7 @@ module ActionDispatch def draw(name) path = @draw_paths.find do |_path| - _path.join("#{name}.rb").file? + File.exists? "#{_path}/#{name}.rb" end unless path @@ -1327,8 +1328,8 @@ module ActionDispatch raise ArgumentError, msg end - route_path = path.join("#{name}.rb") - instance_eval(route_path.read, route_path.to_s) + route_path = "#{path}/#{name}.rb" + instance_eval(File.read(route_path), route_path.to_s) end # match 'path' => 'controller#action' diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 817cdb2d4e..8fde667108 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -51,6 +51,7 @@ module ActionDispatch # # polymorphic_url([blog, @post]) # calls blog.post_path(@post) # form_for([blog, @post]) # => "/blog/posts/1" + # module PolymorphicRoutes # Constructs a call to a named RESTful route for the given record and returns the # resulting URL string. For example: @@ -83,6 +84,7 @@ module ActionDispatch # # # the class of a record will also map to the collection # polymorphic_url(Comment) # same as comments_url() + # def polymorphic_url(record_or_hash_or_array, options = {}) if record_or_hash_or_array.kind_of?(Array) record_or_hash_or_array = record_or_hash_or_array.compact diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index d8beba4397..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 @@ -121,6 +130,7 @@ module ActionDispatch # a string. # # match 'accounts/:name' => redirect(SubdomainRedirector.new('api')) + # def redirect(*args, &block) options = args.extract_options! status = options.delete(:status) || 301 diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index c5601a82d6..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. @@ -180,6 +189,7 @@ module ActionDispatch # Also allow options hash, so you can do: # # foo_url(bar, baz, bang, :sort_by => 'baz') + # def define_url_helper(route, name, options) selector = url_helper_name(name, options[:only_path]) @@ -653,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/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 207f56aaea..fd3bed7e8f 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -79,6 +79,7 @@ module ActionDispatch # end # # User.find(1).base_uri # => "/users/1" + # module UrlFor extend ActiveSupport::Concern include PolymorphicRoutes 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 1539b894c9..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 == '/' @@ -140,6 +138,7 @@ module ActionDispatch # end # end # end + # def with_routing old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new if defined?(@controller) && @controller @@ -188,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 == "/" @@ -204,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/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 08fd28d72d..3fdc6688c2 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -17,8 +17,8 @@ module ActionDispatch # a Hash, or a String that is appropriately encoded # (<tt>application/x-www-form-urlencoded</tt> or # <tt>multipart/form-data</tt>). - # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will - # automatically be upcased, with the prefix 'HTTP_' added if needed. + # - +headers+: Additional headers to pass, as a Hash. The headers will be + # merged into the Rack env hash. # # This method returns an Response object, which one can use to # inspect the details of the response. Furthermore, if this method was @@ -73,8 +73,7 @@ module ActionDispatch # # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart - # string; the headers are a hash. Keys are automatically upcased and - # prefixed with 'HTTP_' if not already. + # string; the headers are a hash. def xml_http_request(request_method, path, parameters = nil, headers = nil) headers ||= {} headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' 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/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index d7df9ea0d5..57b0627225 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -139,6 +139,7 @@ module ActionView # you have too many stylesheets for IE to load. # # stylesheet_link_tag :all, :concat => true + # def stylesheet_link_tag(*sources) @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths) @stylesheet_include.include_tag(*sources) diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index c1f47a2eac..397738dd98 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -33,6 +33,7 @@ module ActionView # <body> # <b><%= @greeting %></b> # </body></html> + # def capture(*args) value = nil buffer = with_output_buffer { value = yield(*args) } diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 61e7a89585..ac150882b1 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -342,7 +342,7 @@ module ActionView # Example: # # <%= form_for(@post) do |f| %> - # <% f.fields_for(:comments, :include_id => false) do |cf| %> + # <%= f.fields_for(:comments, :include_id => false) do |cf| %> # ... # <% end %> # <% end %> @@ -763,6 +763,7 @@ module ActionView # # text_field(:snippet, :code, :size => 20, :class => 'code_input') # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> + # def text_field(object_name, method, options = {}) Tags::TextField.new(object_name, method, self, options).render end @@ -784,6 +785,7 @@ module ActionView # # password_field(:account, :pin, :size => 20, :class => 'form_input') # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" /> + # def password_field(object_name, method, options = {}) Tags::PasswordField.new(object_name, method, self, options).render end @@ -822,6 +824,7 @@ module ActionView # # file_field(:attachment, :file, :class => 'file_input') # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> + # def file_field(object_name, method, options = {}) Tags::FileField.new(object_name, method, self, options).render end @@ -910,6 +913,7 @@ module ActionView # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no") # # => <input name="eula[accepted]" type="hidden" value="no" /> # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> + # def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render end @@ -935,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. @@ -962,6 +975,7 @@ module ActionView # # telephone_field("user", "phone") # # => <input id="user_phone" name="user[phone]" type="tel" /> + # def telephone_field(object_name, method, options = {}) Tags::TelField.new(object_name, method, self, options).render end @@ -980,6 +994,7 @@ module ActionView # @user.born_on = Date.new(1984, 1, 27) # date_field("user", "born_on", value: "1984-05-12") # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" /> + # def date_field(object_name, method, options = {}) Tags::DateField.new(object_name, method, self, options).render end @@ -996,14 +1011,84 @@ module ActionView # === Example # time_field("task", "started_at") # # => <input id="task_started_at" name="task[started_at]" type="time" /> + # def time_field(object_name, method, options = {}) 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") # # => <input id="user_homepage" name="user[homepage]" type="url" /> + # def url_field(object_name, method, options = {}) Tags::UrlField.new(object_name, method, self, options).render end @@ -1012,6 +1097,7 @@ module ActionView # # email_field("user", "address") # # => <input id="user_address" name="user[address]" type="email" /> + # def email_field(object_name, method, options = {}) Tags::EmailField.new(object_name, method, self, options).render end @@ -1191,6 +1277,7 @@ module ActionView # submit: # post: # create: "Add %{model}" + # def submit(value=nil, options={}) value, options = nil, value if value.is_a?(Hash) value ||= submit_default_value @@ -1223,6 +1310,7 @@ module ActionView # submit: # post: # create: "Add %{model}" + # def button(value=nil, options={}) value, options = nil, value if value.is_a?(Hash) value ||= submit_default_value diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 90fa1f3520..eef426703d 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -98,6 +98,7 @@ module ActionView # <option value="3">Jokes</option> # <option value="4">Poems</option> # </select> + # module FormOptionsHelper # ERB::Util can mask some helpers like textilize. Make sure to include them. include TextHelper @@ -153,6 +154,7 @@ module ActionView # key in the query string, that works for ordinary forms. # # In case if you don't want the helper to generate this hidden field you can specify <tt>:include_blank => false</tt> option. + # def select(object, method, choices, options = {}, html_options = {}) Tags::Select.new(object, method, self, choices, options, html_options).render end @@ -239,6 +241,7 @@ module ActionView # <option value="2">Ireland</option> # </optgroup> # </select> + # def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 07453c4b50..1a0019a48c 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -63,6 +63,7 @@ module ActionView # # form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae") # # form with custom authenticity token + # def form_tag(url_for_options = {}, options = {}, &block) html_options = html_options_for_form(url_for_options, options) if block_given? @@ -408,6 +409,7 @@ module ActionView # # submit_tag "Save", :confirm => "Are you sure?" # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" /> + # def submit_tag(value = "Save changes", options = {}) options = options.stringify_keys @@ -444,6 +446,7 @@ module ActionView # # => <button name="button" type="button"> # # <strong>Ask me!</strong> # # </button> + # def button_tag(content_or_options = nil, options = nil, &block) options = content_or_options if block_given? && content_or_options.is_a?(Hash) options ||= {} @@ -521,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 @@ -557,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/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 9e43a1faf1..dfc26acfad 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -514,6 +514,7 @@ module ActionView # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters" # number_to_human(1, :units => :distance) # => "1 meter" # number_to_human(0.34, :units => :distance) # => "34 centimeters" + # def number_to_human(number, options = {}) options = options.symbolize_keys diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb index 891dd859fa..2e7e9dc50c 100644 --- a/actionpack/lib/action_view/helpers/output_safety_helper.rb +++ b/actionpack/lib/action_view/helpers/output_safety_helper.rb @@ -26,6 +26,7 @@ module ActionView #:nodoc: # # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe) # # => "<p>foo</p><br /><p>bar</p>" + # def safe_join(array, sep=$,) sep = ERB::Util.html_escape(sep) diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 8734136f39..9b35f076e5 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -29,6 +29,7 @@ module ActionView # # <div id="person_123" class="person foo"> Joe Bloggs </div> # <div id="person_124" class="person foo"> Jane Bloggs </div> + # def div_for(record, *args, &block) content_tag_for(:div, record, *args, &block) end @@ -78,6 +79,7 @@ module ActionView # produces: # # <li id="person_123" class="person bar">... + # def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block) options, prefix = prefix, nil if prefix.is_a?(Hash) diff --git a/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionpack/lib/action_view/helpers/rendering_helper.rb index 55fb443929..626e1a1ab7 100644 --- a/actionpack/lib/action_view/helpers/rendering_helper.rb +++ b/actionpack/lib/action_view/helpers/rendering_helper.rb @@ -75,6 +75,7 @@ module ActionView # <html> # Hello David # </html> + # def _layout_for(*args, &block) name = args.first diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index ba74217c12..a727b910e5 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -55,6 +55,7 @@ module ActionView # resulting markup is valid (conforming to a document type) or even well-formed. # The output may still contain e.g. unescaped '<', '>', '&' characters and # confuse browsers. + # def sanitize(html, options = {}) self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe) end @@ -143,6 +144,7 @@ module ActionView # class Application < Rails::Application # config.action_view.full_sanitizer = MySpecialSanitizer.new # end + # def full_sanitizer @full_sanitizer ||= HTML::FullSanitizer.new end @@ -153,6 +155,7 @@ module ActionView # class Application < Rails::Application # config.action_view.link_sanitizer = MySpecialSanitizer.new # end + # def link_sanitizer @link_sanitizer ||= HTML::LinkSanitizer.new end @@ -163,6 +166,7 @@ module ActionView # class Application < Rails::Application # config.action_view.white_list_sanitizer = MySpecialSanitizer.new # end + # def white_list_sanitizer @white_list_sanitizer ||= HTML::WhiteListSanitizer.new end @@ -172,6 +176,7 @@ module ActionView # class Application < Rails::Application # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target' # end + # def sanitized_uri_attributes=(attributes) HTML::WhiteListSanitizer.uri_attributes.merge(attributes) end @@ -181,6 +186,7 @@ module ActionView # class Application < Rails::Application # config.action_view.sanitized_bad_tags = 'embed', 'object' # end + # def sanitized_bad_tags=(attributes) HTML::WhiteListSanitizer.bad_tags.merge(attributes) end @@ -190,6 +196,7 @@ module ActionView # class Application < Rails::Application # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # end + # def sanitized_allowed_tags=(attributes) HTML::WhiteListSanitizer.allowed_tags.merge(attributes) end @@ -199,6 +206,7 @@ module ActionView # class Application < Rails::Application # config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc' # end + # def sanitized_allowed_attributes=(attributes) HTML::WhiteListSanitizer.allowed_attributes.merge(attributes) end @@ -208,6 +216,7 @@ module ActionView # class Application < Rails::Application # config.action_view.sanitized_allowed_css_properties = 'expression' # end + # def sanitized_allowed_css_properties=(attributes) HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes) end @@ -217,6 +226,7 @@ module ActionView # class Application < Rails::Application # config.action_view.sanitized_allowed_css_keywords = 'expression' # end + # def sanitized_allowed_css_keywords=(attributes) HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes) end @@ -226,6 +236,7 @@ module ActionView # class Application < Rails::Application # config.action_view.sanitized_shorthand_css_properties = 'expression' # end + # def sanitized_shorthand_css_properties=(attributes) HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes) end @@ -235,6 +246,7 @@ module ActionView # class Application < Rails::Application # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed' # end + # def sanitized_allowed_protocols=(attributes) HTML::WhiteListSanitizer.allowed_protocols.merge(attributes) end 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/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb index 4d94ee2c23..53a108b7e6 100644 --- a/actionpack/lib/action_view/helpers/tags/select.rb +++ b/actionpack/lib/action_view/helpers/tags/select.rb @@ -31,6 +31,7 @@ module ActionView # # [nil, []] # { nil => [] } + # def grouped_choices? !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last 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/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 8cd7cf0052..0cc0d069ea 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -62,9 +62,11 @@ module ActionView # # Pass a <tt>:separator</tt> to truncate +text+ at a natural break. # - # The result is not marked as HTML-safe, so will be subject to the default escaping when - # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags - # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags). + # Pass a block if you want to show extra content when the text is truncated. + # + # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is + # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation + # may produce invalid HTML (such as unbalanced or incomplete tags). # # truncate("Once upon a time in a world far far away") # # => "Once upon a time in a world..." @@ -80,8 +82,18 @@ module ActionView # # truncate("<p>Once upon a time in a world far far away</p>") # # => "<p>Once upon a time in a wo..." - def truncate(text, options = {}) - text.truncate(options.fetch(:length, 30), options) if text + # + # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" } + # # => "Once upon a time in a wo...<a href="#">Continue</a>" + def truncate(text, options = {}, &block) + if text + length = options.fetch(:length, 30) + + content = text.truncate(length, options) + content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content) + content << capture(&block) if block_given? && text.length > length + content + end end # Highlights one or more +phrases+ everywhere in +text+ by inserting it into @@ -102,7 +114,7 @@ module ActionView # # => You searched for: <a href="search?q=rails">rails</a> def highlight(text, phrases, options = {}) highlighter = options.fetch(:highlighter, '<mark>\1</mark>') - + text = sanitize(text) if options.fetch(:sanitize, true) if text.blank? || phrases.blank? text @@ -165,12 +177,12 @@ module ActionView # pluralize(0, 'person') # # => 0 people def pluralize(count, singular, plural = nil) - word = if (count == 1 || count =~ /^1(\.0+)?$/) - singular + word = if (count == 1 || count =~ /^1(\.0+)?$/) + singular else plural || singular.pluralize end - + "#{count || 0} #{word}" end @@ -215,7 +227,7 @@ module ActionView # # simple_format(my_text) # # => "<p>Here is some basic text...\n<br />...with a line break.</p>" - # + # # simple_format(my_text, {}, :wrapper_tag => "div") # # => "<div>Here is some basic text...\n<br />...with a line break.</div>" # @@ -231,7 +243,7 @@ module ActionView # # => "<p><span>I'm allowed!</span> It's true.</p>" def simple_format(text, html_options = {}, options = {}) wrapper_tag = options.fetch(:wrapper_tag, :p) - + text = sanitize(text) if options.fetch(:sanitize, true) paragraphs = split_paragraphs(text) 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/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 2e45a2ae0f..7e69547dab 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -586,6 +586,7 @@ module ActionView # # current_page?(:controller => 'product', :action => 'index') # # => false + # def current_page?(options) unless request raise "You cannot use helpers that need to determine the current " \ 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/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index d267a8466a..fa2038f78d 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -215,6 +215,7 @@ module ActionView # * <tt>:locale</tt> - possible locale versions # * <tt>:formats</tt> - possible request formats (for example html, json, xml...) # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...) + # class FileSystemResolver < PathResolver def initialize(path, pattern=nil) raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) 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..fa4cb301eb 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2331,7 +2331,7 @@ class TestDrawExternalFile < ActionDispatch::IntegrationTest end end - DRAW_PATH = Pathname.new(File.expand_path('../../fixtures/routes', __FILE__)) + DRAW_PATH = File.expand_path('../../fixtures/routes', __FILE__) DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw_paths << DRAW_PATH @@ -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/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index f58e474759..a3ab091c6c 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -60,14 +60,14 @@ class TextHelperTest < ActionView::TestCase simple_format(text) assert_equal text_clone, text end - + def test_simple_format_does_not_modify_the_html_options_hash options = { :class => "foobar"} passed_options = options.dup simple_format("some text", passed_options) assert_equal options, passed_options end - + def test_simple_format_does_not_modify_the_options_hash options = { :wrapper_tag => :div, :sanitize => false } passed_options = options.dup @@ -75,19 +75,11 @@ class TextHelperTest < ActionView::TestCase assert_equal options, passed_options end - def test_truncate_should_not_be_html_safe - assert !truncate("Hello World!", :length => 12).html_safe? - end - def test_truncate assert_equal "Hello World!", truncate("Hello World!", :length => 12) assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12) end - def test_truncate_should_not_escape_input - assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12) - end - def test_truncate_should_use_default_length_of_30 str = "This is a string that will go longer then the default truncate length of 30" assert_equal str[0...27] + "...", truncate(str) @@ -106,7 +98,7 @@ class TextHelperTest < ActionView::TestCase assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10) end - + def test_truncate_does_not_modify_the_options_hash options = { :length => 10 } passed_options = options.dup @@ -114,6 +106,53 @@ class TextHelperTest < ActionView::TestCase assert_equal options, passed_options end + def test_truncate_with_link_options + assert_equal "Here's a long test and I...<a href=\"#\">Continue</a>", + truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + end + + def test_truncate_should_be_html_safe + assert truncate("Hello World!", :length => 12).html_safe? + end + + def test_truncate_should_escape_the_input + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12) + end + + def test_truncate_should_not_escape_the_input_with_escape_false + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false) + end + + def test_truncate_with_escape_false_should_be_html_safe + truncated = truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false) + assert truncated.html_safe? + end + + def test_truncate_with_block_should_be_html_safe + truncated = truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + assert truncated.html_safe? + end + + def test_truncate_with_block_should_escape_the_input + assert_equal "<script>code!</script>He...<a href=\"#\">Continue</a>", + truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + end + + def test_truncate_with_block_should_not_escape_the_input_with_escape_false + assert_equal "<script>code!</script>He...<a href=\"#\">Continue</a>", + truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' } + end + + def test_truncate_with_block_with_escape_false_should_be_html_safe + truncated = truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' } + assert truncated.html_safe? + end + + def test_truncate_with_block_should_escape_the_block + assert_equal "Here's a long test and I...<script>alert('foo');</script>", + truncate("Here's a long test and I need a continue to read link", :length => 27) { "<script>alert('foo');</script>" } + end + def test_highlight_should_be_html_safe assert highlight("This is a beautiful morning", "beautiful").html_safe? end @@ -203,7 +242,7 @@ class TextHelperTest < ActionView::TestCase highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>') ) end - + def test_highlight_does_not_modify_the_options_hash options = { :highlighter => '<b>\1</b>', :sanitize => false } passed_options = options.dup @@ -256,7 +295,7 @@ class TextHelperTest < ActionView::TestCase def test_excerpt_with_utf8 assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8)) end - + def test_excerpt_does_not_modify_the_options_hash options = { :omission => "[...]",:radius => 5 } passed_options = options.dup @@ -271,7 +310,7 @@ class TextHelperTest < ActionView::TestCase def test_word_wrap_with_extra_newlines assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", :line_width => 15)) end - + def test_word_wrap_does_not_modify_the_options_hash options = { :line_width => 15 } passed_options = options.dup 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/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 61985e241b..846d0d7f86 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -53,6 +53,7 @@ module ActiveModel # hash value. # # Hash keys must be strings. + # module AttributeMethods extend ActiveSupport::Concern @@ -195,7 +196,7 @@ module ActiveModel attribute_method_matchers.each do |matcher| matcher_new = matcher.method_name(new_name).to_s matcher_old = matcher.method_name(old_name).to_s - define_optimized_call self, matcher_new, matcher_old + define_proxy_call false, self, matcher_new, matcher_old end end @@ -237,7 +238,7 @@ module ActiveModel if respond_to?(generate_method, true) send(generate_method, attr_name) else - define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s + define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s end end end @@ -292,7 +293,7 @@ module ActiveModel # Define a method `name` in `mod` that dispatches to `send` # using the given `extra` args. This fallbacks `define_method` # and `send` if the given names cannot be compiled. - def define_optimized_call(mod, name, send, *extra) #:nodoc: + def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc: if name =~ NAME_COMPILABLE_REGEXP defn = "def #{name}(*args)" else @@ -302,7 +303,7 @@ module ActiveModel extra = (extra.map(&:inspect) << "*args").join(", ") if send =~ CALL_COMPILABLE_REGEXP - target = "#{send}(#{extra})" + target = "#{"self." unless include_private}#{send}(#{extra})" else target = "send(:'#{send}', #{extra})" end diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 50709e5ee1..ebb4b51aa3 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -83,6 +83,7 @@ module ActiveModel # # obj is the MyModel instance that the callback is being called on # end # end + # def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index cea11c1a18..d7f30f0920 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -22,6 +22,7 @@ module ActiveModel # cm.to_key # => nil # cm.to_param # => nil # cm.to_partial_path # => "contact_messages/contact_message" + # module Conversion extend ActiveSupport::Concern diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 7b311a50ff..aba6618b56 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -317,6 +317,7 @@ module ActiveModel # * <tt>activemodel.errors.messages.blank</tt> # * <tt>errors.attributes.title.blank</tt> # * <tt>errors.messages.blank</tt> + # def generate_message(attribute, type = :invalid, options = {}) type = options.delete(:message) if options[:message].is_a?(Symbol) diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 2c7f2a9334..88b730626c 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -52,6 +52,7 @@ module ActiveModel # # Returns a string giving a relative path. This is used for looking up # partials. For example, a BlogPost model might return "blog_posts/blog_post" + # def test_to_partial_path assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path" assert_kind_of String, model.to_partial_path @@ -73,6 +74,7 @@ module ActiveModel # # Model.model_name must return a string with some convenience methods: # :human, :singular, and :plural. Check ActiveModel::Naming for more information. + # def test_model_naming assert model.class.respond_to?(:model_name), "The model should respond to model_name" model_name = model.class.model_name diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 50951b28d9..cfce1542b1 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -56,11 +56,13 @@ module ActiveModel # # You can specify your own sanitizer object eg. MySanitizer.new. # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for example implementation. + # + # module ClassMethods # Attributes named in this macro are protected from mass-assignment # whenever attributes are sanitized before assignment. A role for the # attributes is optional, if no role is provided then :default is used. - # A role can be defined by using the :as option. + # A role can be defined by using the :as option with a symbol or an array of symbols as the value. # # Mass-assignment to these attributes will simply be ignored, to assign # to them you can use direct writer methods. This is meant to protect @@ -126,7 +128,7 @@ module ActiveModel # # Like +attr_protected+, a role for the attributes is optional, # if no role is provided then :default is used. A role can be defined by - # using the :as option. + # using the :as option with a symbol or an array of symbols as the value. # # This is the opposite of the +attr_protected+ macro: Mass-assignment # will only set attributes in this list, to assign to the rest of diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 117c19c412..f5ea285ccb 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -124,6 +124,7 @@ module ActiveModel # # This will call +custom_notification+, passing as arguments # the current object and :foo. + # def notify_observers(method, *extra_args) self.class.notify_observers(method, self, *extra_args) end @@ -191,6 +192,7 @@ module ActiveModel # If you're using an Observer in a Rails application with Active Record, be sure to # read about the necessary configuration in the documentation for # ActiveRecord::Observer. + # class Observer include Singleton extend ActiveSupport::DescendantsTracker diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 5fabfb7219..6d8fd21814 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -115,6 +115,7 @@ module ActiveModel # @data[key] # end # end + # alias :read_attribute_for_serialization :send # Add associations specified via the <tt>:include</tt> option. 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/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 019d4b22cf..611e9ffd55 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -38,6 +38,7 @@ module ActiveModel # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+ method # to your instances initialized with a new <tt>ActiveModel::Errors</tt> object, so # there is no need for you to do this manually. + # module Validations extend ActiveSupport::Concern @@ -221,6 +222,7 @@ module ActiveModel # @data[key] # end # end + # alias :read_attribute_for_validation :send protected diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index b6a1cfcf41..d94c4e3f4f 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -77,6 +77,7 @@ module ActiveModel # Or to all at the same time: # # validates :password, :presence => true, :confirmation => true, :if => :password_required? + # def validates(*attributes) defaults = attributes.extract_options!.dup validations = defaults.slice!(*_validates_default_keys) diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index de1a271dde..2953126c3c 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -94,6 +94,7 @@ module ActiveModel #:nodoc: # # This setup method is only called when used with validation macros or the # class level <tt>validates_with</tt> method. + # class Validator attr_reader :options @@ -101,6 +102,7 @@ module ActiveModel #:nodoc: # # PresenceValidator.kind # => :presence # UniquenessValidator.kind # => :uniqueness + # def self.kind @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous? end diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index a9db29ee21..e2f2cecc09 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -76,6 +76,19 @@ private end end +class ModelWithRubyKeywordNamedAttributes + include ActiveModel::AttributeMethods + + def attributes + { :begin => 'value of begin', :end => 'value of end' } + end + +private + def attribute(name) + attributes[name.to_sym] + end +end + class ModelWithoutAttributesMethod include ActiveModel::AttributeMethods end @@ -155,6 +168,15 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar end + test '#alias_attribute works with attributes named as a ruby keyword' do + ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end]) + ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin) + ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end) + + assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from + assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to + end + test '#undefine_attribute_methods removes attribute methods' do ModelWithAttributes.define_attribute_methods(:foo) ModelWithAttributes.undefine_attribute_methods 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/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 0ea9f5393d..6b6aad3bd1 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -65,6 +65,7 @@ class I18nValidationTest < ActiveModel::TestCase # A set of common cases for ActiveModel::Validations message generation that # are used to generate tests to keep things DRY + # COMMON_CASES = [ # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], 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 e0392c5a48..3ae7030caa 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -166,6 +166,7 @@ module ActiveRecord # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": # # Customer.where(:balance => Money.new(20, "USD")).all + # module ClassMethods # Adds reader and writer methods for manipulating a value object: # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods. @@ -192,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) @@ -206,6 +208,7 @@ module ActiveRecord # :mapping => %w(ip to_i), # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } + # def composed_of(part_id, options = {}) options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) @@ -239,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/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 57d602fd83..e75003f261 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -34,6 +34,7 @@ module ActiveRecord # Returns the name of the table of the related class: # # post.comments.aliased_table_name # => "comments" + # def aliased_table_name klass.table_name end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 647d495d56..294aa63f75 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -39,6 +39,10 @@ module ActiveRecord ## # :method: select # + # :call-seq: + # select(select = nil) + # select(&block) + # # Works in two ways. # # *First:* Specify a subset of fields to be selected from the result set. @@ -88,9 +92,17 @@ module ActiveRecord # # ] # # person.pets.select(:name) { |pet| pet.name =~ /oo/ } + # # => [ + # # #<Pet id: 2, name: "Spook">, + # # #<Pet id: 3, name: "Choo-Choo"> + # # ] ## # :method: find + # + # :call-seq: + # find(*args, &block) + # # Finds an object in the collection responding to the +id+. Uses the same # rules as +ActiveRecord::Base.find+. Returns +ActiveRecord::RecordNotFound++ # error if the object can not be found. @@ -105,14 +117,27 @@ module ActiveRecord # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> # # ] - # + # # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1> # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4 + # + # person.pets.find(2) { |pet| pet.name.downcase! } + # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1> + # + # person.pets.find(2, 3) + # # => [ + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] ## # :method: first + # + # :call-seq: + # first(limit = nil) + # # Returns the first record, or the first +n+ records, from the collection. - # If the collection is empty, the first form returns nil, and the second + # If the collection is empty, the first form returns +nil+, and the second # form returns an empty array. # # class Person < ActiveRecord::Base @@ -140,8 +165,12 @@ module ActiveRecord ## # :method: last + # + # :call-seq: + # last(limit = nil) + # # Returns the last record, or the last +n+ records, from the collection. - # If the collection is empty, the first form returns nil, and the second + # If the collection is empty, the first form returns +nil+, and the second # form returns an empty array. # # class Person < ActiveRecord::Base @@ -168,7 +197,95 @@ module ActiveRecord # another_person_without.pets.last(3) # => [] ## + # :method: build + # + # :call-seq: + # build(attributes = {}, options = {}, &block) + # + # Returns a new object of the collection type that has been instantiated + # with +attributes+ and linked to this object, but have not yet been saved. + # You can pass an array of attributes hashes, this will return an array + # with the new objects. + # + # class Person + # has_many :pets + # end + # + # person.pets.build + # # => #<Pet id: nil, name: nil, person_id: 1> + # + # person.pets.build(name: 'Fancy-Fancy') + # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1> + # + # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}]) + # # => [ + # # #<Pet id: nil, name: "Spook", person_id: 1>, + # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>, + # # #<Pet id: nil, name: "Brain", person_id: 1> + # # ] + # + # person.pets.size # => 5 # size of the collection + # person.pets.count # => 0 # count from database + + ## + # :method: create + # + # :call-seq: + # create(attributes = {}, options = {}, &block) + # + # Returns a new object of the collection type that has been instantiated with + # attributes, linked to this object and that has already been saved (if it + # passes the validations). + # + # class Person + # has_many :pets + # end + # + # person.pets.create(name: 'Fancy-Fancy') + # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1> + # + # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}]) + # # => [ + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.size # => 3 + # person.pets.count # => 3 + # + # person.pets.find(1, 2, 3) + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + + ## + # :method: create! + # + # :call-seq: + # create!(attributes = {}, options = {}, &block) + # + # Like +create+, except that if the record is invalid, raises an exception. + # + # class Person + # has_many :pets + # end + # + # class Pet + # attr_accessible :name + # validates :name, presence: true + # end + # + # person.pets.create!(name: nil) + # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank + + ## # :method: concat + # + # :call-seq: + # concat(*records) + # # Add one or more records to the collection by setting their foreign keys # to the association's primary key. Since << flattens its argument list and # inserts each record, +push+ and +concat+ behave identically. Returns +self+ @@ -196,6 +313,10 @@ module ActiveRecord ## # :method: replace + # + # :call-seq: + # replace(other_array) + # # Replace this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. # @@ -220,23 +341,399 @@ module ActiveRecord # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String ## + # :method: delete_all + # + # :call-seq: + # delete_all() + # + # Deletes all the records from the collection. For +has_many+ asssociations, + # the deletion is done according to the strategy specified by the <tt>:dependent</tt> + # option. Returns an array with the deleted records. + # + # If no <tt>:dependent</tt> option is given, then it will follow the + # default strategy. The default strategy is <tt>:nullify</tt>. This + # sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, + # the default strategy is +delete_all+. + # + # class Person < ActiveRecord::Base + # has_many :pets # dependent: :nullify option by default + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.delete_all + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(1, 2, 3) + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>, + # # #<Pet id: 2, name: "Spook", person_id: nil>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: nil> + # # ] + # + # If it is set to <tt>:destroy</tt> all the objects from the collection + # are removed by calling their +destroy+ method. See +destroy+ for more + # information. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :destroy + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.delete_all + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # Pet.find(1, 2, 3) + # # => ActiveRecord::RecordNotFound + # + # If it is set to <tt>:delete_all</tt>, all the objects are deleted + # *without* calling their +destroy+ method. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :delete_all + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.delete_all + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # Pet.find(1, 2, 3) + # # => ActiveRecord::RecordNotFound + + ## # :method: destroy_all - # Destroy all the records from this association. + # + # :call-seq: + # destroy_all() + # + # Deletes the records of the collection directly from the database. + # This will _always_ remove the records ignoring the +:dependent+ + # option. # # class Person < ActiveRecord::Base # has_many :pets # end # # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] # # person.pets.destroy_all # # person.pets.size # => 0 # person.pets # => [] + # + # Pet.find(1) # => Couldn't find Pet with id=1 + + ## + # :method: delete + # + # :call-seq: + # delete(*records) + # + # Deletes the +records+ supplied and removes them from the collection. For + # +has_many+ associations, the deletion is done according to the strategy + # specified by the <tt>:dependent</tt> option. Returns an array with the + # deleted records. + # + # If no <tt>:dependent</tt> option is given, then it will follow the default + # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign + # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default + # strategy is +delete_all+. + # + # class Person < ActiveRecord::Base + # has_many :pets # dependent: :nullify option by default + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.delete(Pet.find(1)) + # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # Pet.find(1) + # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil> + # + # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling + # their +destroy+ method. See +destroy+ for more information. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :destroy + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.delete([Pet.find(1), Pet.find(3)]) + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.size # => 1 + # person.pets + # # => [#<Pet id: 2, name: "Spook", person_id: 1>] + # + # Pet.find(1, 3) + # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3) + # + # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted + # *without* calling their +destroy+ method. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :delete_all + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.delete(Pet.find(1)) + # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # Pet.find(1) + # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1 + + ## + # :method: destroy + # + # :call-seq: + # destroy(*records) + # + # Destroys the +records+ supplied and removes them from the collection. + # This method will _always_ remove record from the database ignoring + # the +:dependent+ option. Returns an array with the removed records. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.destroy(Pet.find(1)) + # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.destroy(Pet.find(2), Pet.find(3)) + # # => [ + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3) + # + # You can pass +Fixnum+ or +String+ values, it finds the records + # responding to the +id+ and then deletes them from the database. + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #<Pet id: 4, name: "Benny", person_id: 1>, + # # #<Pet id: 5, name: "Brain", person_id: 1>, + # # #<Pet id: 6, name: "Boss", person_id: 1> + # # ] + # + # person.pets.destroy("4") + # # => #<Pet id: 4, name: "Benny", person_id: 1> + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #<Pet id: 5, name: "Brain", person_id: 1>, + # # #<Pet id: 6, name: "Boss", person_id: 1> + # # ] + # + # person.pets.destroy(5, 6) + # # => [ + # # #<Pet id: 5, name: "Brain", person_id: 1>, + # # #<Pet id: 6, name: "Boss", person_id: 1> + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6) + + ## + # :method: uniq + # + # :call-seq: + # uniq() + # + # Specifies whether the records should be unique or not. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.select(:name) + # # => [ + # # #<Pet name: "Fancy-Fancy">, + # # #<Pet name: "Fancy-Fancy"> + # # ] + # + # person.pets.select(:name).uniq + # # => [#<Pet name: "Fancy-Fancy">] + + ## + # :method: count + # + # :call-seq: + # count() + # + # Count all records using SQL. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.count # => 3 + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + + ## + # :method: size + # + # :call-seq: + # size() + # + # Returns the size of the collection. If the collection hasn't been loaded, + # it executes a <tt>SELECT COUNT(*)</tt> query. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1 + # + # person.pets # This will execute a SELECT * FROM query + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.size # => 3 + # # Because the collection is already loaded, this will behave like + # # collection.size and no SQL count query is executed. + + ## + # :method: length + # + # :call-seq: + # length() + # + # Returns the size of the collection calling +size+ on the target. + # If the collection has been already loaded, +length+ and +size+ are + # equivalent. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.length # => 3 + # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1 + # + # # Because the collection is loaded, you can + # # call the collection with no additional queries: + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] ## # :method: empty? - # Returns true if the collection is empty. + # + # Returns +true+ if the collection is empty. # # class Person < ActiveRecord::Base # has_many :pets @@ -252,7 +749,12 @@ module ActiveRecord ## # :method: any? - # Returns true if the collection is not empty. + # + # :call-seq: + # any? + # any?{|item| block} + # + # Returns +true+ if the collection is not empty. # # class Person < ActiveRecord::Base # has_many :pets @@ -284,8 +786,13 @@ module ActiveRecord ## # :method: many? + # + # :call-seq: + # many? + # many?{|item| block} + # # Returns true if the collection has more than one record. - # Equivalent to +collection.size > 1+. + # Equivalent to <tt>collection.size > 1</tt>. # # class Person < ActiveRecord::Base # has_many :pets @@ -321,7 +828,11 @@ module ActiveRecord ## # :method: include? - # Returns true if the given object is present in the collection. + # + # :call-seq: + # include?(record) + # + # Returns +true+ if the given object is present in the collection. # # class Person < ActiveRecord::Base # has_many :pets @@ -337,8 +848,8 @@ module ActiveRecord :sum, :count, :size, :length, :empty?, :any?, :many?, :include?, :to => :@association - - def initialize(association) + + def initialize(association) #:nodoc: @association = association super association.klass, association.klass.arel_table merge! association.scoped @@ -370,10 +881,67 @@ module ActiveRecord end end + # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays + # contain the same number of elements and if each element is equal + # to the corresponding element in the other array, otherwise returns + # +false+. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1> + # # ] + # + # other = person.pets.to_ary + # + # person.pets == other + # # => true + # + # other = [Pet.new(id: 1), Pet.new(id: 2)] + # + # person.pets == other + # # => false def ==(other) load_target == other end + # Returns a new array of objects from the collection. If the collection + # hasn't been loaded, it fetches the records from the database. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #<Pet id: 4, name: "Benny", person_id: 1>, + # # #<Pet id: 5, name: "Brain", person_id: 1>, + # # #<Pet id: 6, name: "Boss", person_id: 1> + # # ] + # + # other_pets = person.pets.to_ary + # # => [ + # # #<Pet id: 4, name: "Benny", person_id: 1>, + # # #<Pet id: 5, name: "Brain", person_id: 1>, + # # #<Pet id: 6, name: "Boss", person_id: 1> + # # ] + # + # other_pets.replace([Pet.new(name: 'BooGoo')]) + # + # other_pets + # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>] + # + # person.pets + # # This is not affected by replace + # # => [ + # # #<Pet id: 4, name: "Benny", person_id: 1>, + # # #<Pet id: 5, name: "Brain", person_id: 1>, + # # #<Pet id: 6, name: "Boss", person_id: 1> + # # ] def to_ary load_target.dup end @@ -404,37 +972,32 @@ module ActiveRecord end alias_method :push, :<< - # Removes every object from the collection. This does not destroy - # the objects, it sets their foreign keys to +NULL+. Returns +self+ - # so methods can be chained. + # Equivalent to +delete_all+. The difference is that returns +self+, instead + # of an array with the deleted objects, so methods can be chained. See + # +delete_all+ for more information. + def clear + delete_all + self + end + + # Reloads the collection from the database. Returns +self+. + # Equivalent to <tt>collection(true)</tt>. # # class Person < ActiveRecord::Base # has_many :pets # end # - # person.pets # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] - # person.pets.clear # => [] - # person.pets.size # => 0 - # - # Pet.find(1) # => #<Pet id: 1, name: "Snoop", group: "dogs", person_id: nil> - # - # If they are associated with +dependent: :destroy+ option, it deletes - # them directly from the database. + # person.pets # fetches pets from the database + # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] # - # class Person < ActiveRecord::Base - # has_many :pets, dependent: :destroy - # end + # person.pets # uses the pets cache + # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] # - # person.pets # => [#<Pet id: 2, name: "Gorby", group: "cats", person_id: 2>] - # person.pets.clear # => [] - # person.pets.size # => 0 + # person.pets.reload # fetches pets from the database + # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] # - # Pet.find(2) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=2 - def clear - delete_all - self - end - + # person.pets(true) # fetches pets from the database + # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] def reload proxy_association.reload self diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index fafed94ff2..54705e4950 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -12,7 +12,7 @@ module ActiveRecord # and all of its books via a single query: # # SELECT * FROM authors - # LEFT OUTER JOIN books ON authors.id = books.id + # LEFT OUTER JOIN books ON authors.id = books.author_id # WHERE authors.name = 'Ken Akamatsu' # # However, this could result in many rows that contain redundant data. After diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index fe18a5d5aa..a050fabf35 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -229,6 +229,7 @@ module ActiveRecord # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead) # # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model. + # module Callbacks # We can't define callbacks directly on ActiveRecord::Model because # it is a module. So we queue up the definitions and execute them 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..c259e46073 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -55,12 +55,20 @@ 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 @@ -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/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 95ab375951..df78ba6c5a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -339,6 +339,7 @@ module ActiveRecord # t.remove_index # t.remove_timestamps # end + # class Table def initialize(table_name, base) @table_name = table_name diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index e2eb72fdd6..62b0f51bb2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -380,6 +380,7 @@ module ActiveRecord # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active # # Note: only supported by PostgreSQL + # def add_index(table_name, column_name, options = {}) index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}" diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 350ccce03d..8fc172f6e8 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -268,7 +268,7 @@ module ActiveRecord # increase timeout so mysql server doesn't disconnect us wait_timeout = @config[:wait_timeout] - wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum) + wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) variable_assignments << "@@wait_timeout = #{wait_timeout}" execute("SET #{variable_assignments.join(', ')}", :skip_logging) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 3d6ab72e07..0b6734b010 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -59,6 +59,7 @@ module ActiveRecord # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection. + # class MysqlAdapter < AbstractMysqlAdapter class Column < AbstractMysqlAdapter::Column #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index ad7c4025cf..7dcea375e1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -18,7 +18,7 @@ module ActiveRecord # Forward any unused config params to PGconn.connect. [:statement_limit, :encoding, :min_messages, :schema_search_path, - :schema_order, :adapter, :pool, :wait_timeout, :template, + :schema_order, :adapter, :pool, :checkout_timeout, :template, :reaping_frequency, :insert_returning].each do |key| conn_params.delete key end @@ -684,6 +684,7 @@ module ActiveRecord # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) # Filter: (posts.user_id = 1) # (6 rows) + # def pp(result) header = result.columns.first lines = result.rows.map(&:first) @@ -963,22 +964,22 @@ module ActiveRecord binds = [[nil, table]] binds << [nil, schema] if schema - exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0 + exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 SELECT COUNT(*) FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind in ('v','r') - AND c.relname = $1 - AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'} + AND c.relname = '#{table.gsub(/(^"|"$)/,'')}' + AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'} SQL end # Returns true if schema exists. def schema_exists?(name) - exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0 + exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 SELECT COUNT(*) FROM pg_namespace - WHERE nspname = $1 + WHERE nspname = '#{name}' SQL end @@ -1109,8 +1110,8 @@ module ActiveRecord end def serial_sequence(table, column) - result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]]) - SELECT pg_get_serial_sequence($1, $2) + result = exec_query(<<-eosql, 'SCHEMA') + SELECT pg_get_serial_sequence('#{table}', '#{column}') eosql result.rows.first.first end @@ -1187,13 +1188,13 @@ module ActiveRecord # Returns just a table's primary key def primary_key(table) - row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first + row = exec_query(<<-end_sql, 'SCHEMA').rows.first SELECT DISTINCT(attr.attname) FROM pg_attribute attr INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1] WHERE cons.contype = 'p' - AND dep.refobjid = $1::regclass + AND dep.refobjid = '#{table}'::regclass end_sql row && row.first @@ -1273,7 +1274,7 @@ module ActiveRecord end when 'integer' return 'integer' unless limit - + case limit when 1, 2; 'smallint' when 3, 4; 'integer' @@ -1335,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 @@ -1466,7 +1471,7 @@ module ActiveRecord end def last_insert_id_result(sequence_name) #:nodoc: - exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]]) + exec_query("SELECT currval('#{sequence_name}')", 'SQL') end # Executes a SELECT query and returns the results, performing any data type diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 25ec88c9c5..d4ffa82b17 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -265,6 +265,7 @@ module ActiveRecord # # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) # 0|1|1|SCAN TABLE posts (~100000 rows) + # def pp(result) # :nodoc: result.rows.map do |row| row.join('|') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index f2833fbf3c..80c6f20b1a 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -10,9 +10,10 @@ module ActiveRecord included do ## # :singleton-method: - # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, - # which is then passed on to any new database connections made and which can be retrieved on both - # a class and instance level by calling +logger+. + # + # Accepts a logger conforming to the interface of Log4r which is then + # passed on to any new database connections made and which can be + # retrieved on both a class and instance level by calling +logger+. config_attribute :logger, :global => true ## diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 604da5a4fd..a3412582fa 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -46,6 +46,7 @@ module ActiveRecord # class Person < ActiveRecord::Base # self.locking_column = :lock_person # end + # module Optimistic extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb index 5906a57828..105d1e0e2b 100644 --- a/activerecord/lib/active_record/model.rb +++ b/activerecord/lib/active_record/model.rb @@ -7,6 +7,7 @@ module ActiveRecord # class Post # include ActiveRecord::Model # end + # module Model module ClassMethods #:nodoc: include ActiveSupport::Callbacks::ClassMethods diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 2467edcc53..fdf17c003c 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -87,6 +87,7 @@ module ActiveRecord # If by any chance you are using observed models in the initialization you can still # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are # singletons and that call instantiates and registers them. + # class Observer < ActiveModel::Observer protected diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index daf5936a2d..a1bc39a32d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -158,6 +158,7 @@ module ActiveRecord # * Callbacks are invoked. # * updated_at/updated_on column is updated if that column is available. # * Updates all the attributes that are dirty in this object. + # def update_attribute(name, value) name = name.to_s verify_readonly_attribute(name) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f26e18b1e0..d8d4834d22 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -406,10 +406,11 @@ db_namespace = namespace :db do set_psql_env(abcs[Rails.env]) search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? - search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ") + search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ") end - `pg_dump -i -s -x -O -f #{filename} #{search_path} #{abcs[Rails.env]['database']}` + `pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(abcs[Rails.env]['database'])}` raise 'Error dumping database' if $?.exitstatus == 1 + File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" } when /sqlite/ dbfile = abcs[Rails.env]['database'] `sqlite3 #{dbfile} .schema > #{filename}` diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 17e388a1d4..c380b5c029 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -42,6 +42,7 @@ module ActiveRecord # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). # # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection + # def reflect_on_aggregation(aggregation) reflection = reflections[aggregation] reflection if reflection.is_a?(AggregateReflection) @@ -56,6 +57,7 @@ module ActiveRecord # # Account.reflect_on_all_associations # returns an array of all associations # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations + # def reflect_on_all_associations(macro = nil) association_reflections = reflections.values.grep(AssociationReflection) macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections @@ -65,6 +67,7 @@ module ActiveRecord # # Account.reflect_on_association(:owner) # returns the owner AssociationReflection # Invoice.reflect_on_association(:line_items).macro # returns :has_many + # def reflect_on_association(association) reflection = reflections[association] reflection if reflection.is_a?(AssociationReflection) @@ -383,6 +386,7 @@ module ActiveRecord # has_many :taggings # has_many :tags, :through => :taggings # end + # def source_reflection @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first end @@ -397,6 +401,7 @@ module ActiveRecord # # tags_reflection = Post.reflect_on_association(:tags) # taggings_reflection = tags_reflection.through_reflection + # def through_reflection @through_reflection ||= active_record.reflect_on_association(options[:through]) end @@ -483,6 +488,7 @@ module ActiveRecord # Gets an array of possible <tt>:through</tt> source reflection names: # # [:singularized, :pluralized] + # def source_reflection_names @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym } end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 75d983d3b8..ab32bfe70a 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -118,7 +118,7 @@ module ActiveRecord # Person.all.map(&:name) # # Pluck returns an <tt>Array</tt> of attribute values type-casted to match - # the plucked column name, if it can be deduced. Plucking a SQL fragment + # the plucked column name, if it can be deduced. Plucking an SQL fragment # returns String values by default. # # Examples: @@ -138,6 +138,7 @@ module ActiveRecord # Person.pluck('DATEDIFF(updated_at, created_at)') # # SELECT DATEDIFF(updated_at, created_at) FROM people # # => ['0', '27761', '173'] + # def pluck(column_name) if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s) column_name = "#{table_name}.#{column_name}" diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c1783dba72..0abb801074 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -49,6 +49,7 @@ module ActiveRecord # # Post.find_by name: 'Spartacus', rating: 4 # Post.find_by "published_at < ?", 2.weeks.ago + # def find_by(*args) where(*args).take end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c086386da6..19fe8155d9 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -163,6 +163,7 @@ module ActiveRecord # User.order('email DESC').reorder('id ASC').order('name ASC') # # generates a query with 'ORDER BY id ASC, name ASC'. + # def reorder(*args) args.blank? ? self : spawn.reorder!(*args) end @@ -276,6 +277,7 @@ module ActiveRecord # Post.none # => returning [] instead breaks the previous code # end # end + # def none NullRelation.new(@klass, @table) end @@ -310,6 +312,7 @@ module ActiveRecord # # Topics.select('a.title').from(Topics.approved, :a) # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a + # def from(value, subquery_name = nil) spawn.from!(value, subquery_name) end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 4fb1cb6726..80d087a9ea 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -23,6 +23,7 @@ module ActiveRecord # Post.where(:published => true).merge(recent_posts) # # Returns the intersection of all published posts with the 5 most recently created posts. # # (This is just an example. You'd probably want to do this with a single query!) + # def merge(other) if other.is_a?(Array) to_a & other @@ -44,6 +45,7 @@ module ActiveRecord # # Post.order('id asc').except(:order) # discards the order condition # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order + # def except(*skips) result = Relation.new(klass, table, values.except(*skips)) result.default_scoped = default_scoped @@ -57,6 +59,7 @@ module ActiveRecord # # Post.order('id asc').only(:where) # discards the order condition # Post.order('id asc').only(:where, :order) # uses the specified order + # def only(*onlies) result = Relation.new(klass, table, values.slice(*onlies)) result.default_scoped = default_scoped 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/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index c1a72ea8b3..9e4b588ac2 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -198,6 +198,7 @@ module ActiveRecord # * ActiveRecord::ConnectionAdapters::Mysql2Adapter # * ActiveRecord::ConnectionAdapters::SQLite3Adapter # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter + # def validates_uniqueness_of(*attr_names) validates_with UniquenessValidator, _merge_attributes(attr_names) 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/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index eef1366a6d..1093fedea1 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -719,6 +719,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_read_attribute_overwrites_private_method_not_considered_implemented # simulate a model with a db column that shares its name an inherited # private method (e.g. Object#system) + # Object.class_eval do private def title; "private!"; end 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/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 89b92e0b13..15b97c02c8 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -33,6 +33,7 @@ class I18nValidationTest < ActiveRecord::TestCase # A set of common cases for ActiveModel::Validations message generation that # are used to generate tests to keep things DRY + # COMMON_CASES = [ # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], 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/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb index 6bf45ba5b2..8e28f8b86b 100644 --- a/activerecord/test/models/subject.rb +++ b/activerecord/test/models/subject.rb @@ -1,4 +1,5 @@ # used for OracleSynonymTest, see test/synonym_test_oracle.rb +# class Subject < ActiveRecord::Base # added initialization of author_email_address in the same way as in Topic class diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 636aca2b8f..62b8a789c7 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,17 @@ ## Rails 4.0.0 (unreleased) ## +* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_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/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index a39fa016ec..f149a7f0ed 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -44,6 +44,7 @@ module ActiveSupport end # Silence the logger during the execution of the block. + # def silence old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger yield diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 55791bfa56..a62214d604 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -405,7 +405,7 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support increment") end - # Increment an integer value in the cache. + # Decrement an integer value in the cache. # # Options are passed to the underlying cache implementation. # 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/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 9636bbccb5..a9253c186d 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -341,6 +341,7 @@ module ActiveSupport # This is used internally to append, prepend and skip callbacks to the # CallbackChain. + # def __update_callbacks(name, filters = [], block = nil) #:nodoc: type = filters.first.in?([:before, :after, :around]) ? filters.shift : :before options = filters.last.is_a?(Hash) ? filters.pop : {} diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index 9450d4ea88..c94a8d99f4 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -94,6 +94,7 @@ module ActiveSupport # class Host # include Bar # works, Bar takes care now of its dependencies # end + # module Concern def self.extended(base) base.instance_variable_set("@_dependencies", []) diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index afd56cb7ce..a8aa53a80f 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -48,6 +48,7 @@ module ActiveSupport # user = User.new # user.allowed_access = true # user.allowed_access # => true + # def config_accessor(*names) options = names.extract_options! @@ -78,6 +79,7 @@ module ActiveSupport # # user.config.allowed_access # => true # user.config.level # => 1 + # def config @_config ||= self.class.config.inheritable_copy end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 44d90ef732..a8f9dddae5 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -1,40 +1,48 @@ class Array # Returns the tail of the array from +position+. # - # %w( a b c d ).from(0) # => %w( a b c d ) - # %w( a b c d ).from(2) # => %w( c d ) - # %w( a b c d ).from(10) # => %w() - # %w().from(0) # => %w() + # %w( a b c d ).from(0) # => ["a", "b", "c", "d"] + # %w( a b c d ).from(2) # => ["c", "d"] + # %w( a b c d ).from(10) # => [] + # %w().from(0) # => [] def from(position) self[position, length] || [] end # Returns the beginning of the array up to +position+. # - # %w( a b c d ).to(0) # => %w( a ) - # %w( a b c d ).to(2) # => %w( a b c ) - # %w( a b c d ).to(10) # => %w( a b c d ) - # %w().to(0) # => %w() + # %w( a b c d ).to(0) # => ["a"] + # %w( a b c d ).to(2) # => ["a", "b", "c"] + # %w( a b c d ).to(10) # => ["a", "b", "c", "d"] + # %w().to(0) # => [] def to(position) first position + 1 end # Equal to <tt>self[1]</tt>. + # + # %w( a b c d e).second # => "b" def second self[1] end # Equal to <tt>self[2]</tt>. + # + # %w( a b c d e).third # => "c" def third self[2] end # Equal to <tt>self[3]</tt>. + # + # %w( a b c d e).fourth # => "d" def fourth self[3] end # Equal to <tt>self[4]</tt>. + # + # %w( a b c d e).fifth # => "e" def fifth self[4] end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index d5735dd7eb..2af87d3b6b 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -4,10 +4,57 @@ require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/string/inflections' class Array - # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options: - # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ") - # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ") - # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ") + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. + # + # You can pass the following options to change the default behaviour. If you + # pass an option key that doesn't exist in the next list, it will raise an + # "Unknow key" error. + # + # Options: + # + # * <tt>:words_connector</tt> - The sign or word used to join the elements + # in arrays with two or more elements (default: ", "). + # * <tt>:two_words_connector</tt> - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # * <tt>:last_word_connector</tt> - The sign or word used to join the last element + # in arrays with three or more elements (default: ", and "). + # * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use + # the connector options defined on the 'support.array' namespace in the + # corresponding dictionary file. + # + # Examples: + # + # [].to_sentence # => "" + # ['one'].to_sentence # => "one" + # ['one', 'two'].to_sentence # => "one and two" + # ['one', 'two', 'three'].to_sentence # => "one, two, and three" + # + # ['one', 'two'].to_sentence(passing: 'invalid option') + # # => ArgumentError: Unknown key :passing + # + # ['one', 'two'].to_sentence(two_words_connector: '-') + # # => "one-two" + # + # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') + # # => "one or two or at least three" + # + # Examples using <tt>:locale</tt> option: + # + # # With the next locale dictionary: + # # + # # es: + # # support: + # # array: + # # words_connector: " o " + # # two_words_connector: " y " + # # last_word_connector: " o al menos " + # + # ['uno', 'dos'].to_sentence(locale: :es) + # # => "uno y dos" + # + # ['uno', 'dos', 'tres'].to_sentence(locale: :es) + # # => "uno o dos o al menos tres" def to_sentence(options = {}) options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) @@ -149,6 +196,7 @@ class Array # <user-id>1</user-id> # </message> # </messages> + # def to_xml(options = {}) require 'active_support/builder' unless defined?(Builder) diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index ac1ae53db0..a184eb492a 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -2,18 +2,21 @@ class Array # Splits or iterates over the array in groups of size +number+, # padding any remaining slots with +fill_with+ unless it is +false+. # - # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group} + # %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group} # ["1", "2", "3"] # ["4", "5", "6"] - # ["7", nil, nil] + # ["7", "8", "9"] + # ["10", nil, nil] # - # %w(1 2 3).in_groups_of(2, ' ') {|group| p group} + # %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group} # ["1", "2"] - # ["3", " "] + # ["3", "4"] + # ["5", " "] # - # %w(1 2 3).in_groups_of(2, false) {|group| p group} + # %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group} # ["1", "2"] - # ["3"] + # ["3", "4"] + # ["5"] def in_groups_of(number, fill_with = nil) if fill_with == false collection = self @@ -42,10 +45,10 @@ class Array # ["5", "6", "7", nil] # ["8", "9", "10", nil] # - # %w(1 2 3 4 5 6 7).in_groups(3, ' ') {|group| p group} - # ["1", "2", "3"] - # ["4", "5", " "] - # ["6", "7", " "] + # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group} + # ["1", "2", "3", "4"] + # ["5", "6", "7", " "] + # ["8", "9", "10", " "] # # %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group} # ["1", "2", "3"] diff --git a/activesupport/lib/active_support/core_ext/array/uniq_by.rb b/activesupport/lib/active_support/core_ext/array/uniq_by.rb index c1d5a355a4..3bedfa9a61 100644 --- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb +++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb @@ -4,6 +4,7 @@ class Array # Returns a unique array based on the criteria in the block. # # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2] + # def uniq_by(&block) ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead', caller uniq(&block) 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/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 74ea047c24..c2e0ebb3d4 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -1,11 +1,11 @@ require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/reachable' -class Class #:nodoc: +class Class begin ObjectSpace.each_object(Class.new) {} - def descendants + def descendants # :nodoc: descendants = [] ObjectSpace.each_object(singleton_class) do |k| descendants.unshift k unless k == self @@ -13,7 +13,7 @@ class Class #:nodoc: descendants end rescue StandardError # JRuby - def descendants + def descendants # :nodoc: descendants = [] ObjectSpace.each_object(Class) do |k| descendants.unshift k if k < self @@ -25,7 +25,13 @@ class Class #:nodoc: # Returns an array with the direct children of +self+. # - # Integer.subclasses # => [Bignum, Fixnum] + # Integer.subclasses # => [Fixnum, Bignum] + # + # class Foo; end + # class Bar < Foo; end + # class Baz < Foo; end + # + # Foo.subclasses # => [Baz, Bar] def subclasses subclasses, chain = [], descendants chain.each do |k| diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 3e36c54eba..8a7eb6bc6b 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -202,7 +202,7 @@ class Date acts_like?(:time) ? result.change(:hour => 0) : result end - # Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00) + # Returns a new Date/DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00) def beginning_of_month acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1) end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index b950826396..03efe6a19a 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -17,6 +17,7 @@ module Enumerable # The default sum of an empty list is zero. You can override this default: # # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) + # def sum(identity = 0, &block) if block_given? map(&block).sum(identity) @@ -31,6 +32,7 @@ module Enumerable # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...} # people.index_by { |person| "#{person.first_name} #{person.last_name}" } # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...} + # def index_by if block_given? Hash[map { |elem| [yield(elem), elem] }] @@ -63,11 +65,15 @@ class Range #:nodoc: # Optimize range sum to use arithmetic progression if a block is not given and # we have a range of numeric values. def sum(identity = 0) - if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer)) + if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer)) super else actual_last = exclude_end? ? (last - 1) : last - (actual_last - first + 1) * (actual_last + first) / 2 + if actual_last >= first + (actual_last - first + 1) * (actual_last + first) / 2 + else + identity + end end end end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 469dc41f2d..7c72ead36c 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. @@ -129,7 +129,7 @@ class Hash else xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }] - # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with + # Turn { :files => { :file => #<StringIO> } } into { :files => #<StringIO> } so it is compatible with # how multipart uploaded files from HTML appear xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value end diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index fafd4919b9..5a61906222 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -10,6 +10,7 @@ class Hash # # {:a => 1}.with_indifferent_access.except(:a) # => {} # {:a => 1}.with_indifferent_access.except('a') # => {} + # def except(*keys) dup.except!(*keys) end diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index c1f368d0eb..7d54c9fae6 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -5,6 +5,7 @@ class Hash # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver: # # {:a => 1}.with_indifferent_access["a"] # => 1 + # def with_indifferent_access ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self) end @@ -18,5 +19,6 @@ class Hash # # b = {:b => 1} # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access + # alias nested_under_indifferent_access with_indifferent_access end diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index be4d611ce7..8e728691c6 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,46 +1,59 @@ 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 + # hash = { name: 'Rob', age: '28' } + # + # hash.transform_keys{ |key| key.to_s.upcase } + # # => { "NAME" => "Rob", "AGE" => "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. + # + # hash = { name: 'Rob', age: '28' } + # + # hash.stringify_keys + # #=> { "name" => "Rob", "age" => "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" } + # hash = { 'name' => 'Rob', 'age' => '28' } + # + # hash.symbolize_keys + # #=> { name: "Rob", age: "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 +70,69 @@ class Hash raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k) end end + + # Return a new hash with all keys converted by the block operation. + # This includes the keys from the root hash and from all + # nested hashes. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_transform_keys{ |key| key.to_s.upcase } + # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } } + def deep_transform_keys(&block) + result = {} + each do |key, value| + result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value + end + result + end + + # Destructively convert all keys by using the block operation. + # This includes the keys from the root hash and from all + # nested hashes. + def deep_transform_keys!(&block) + keys.each do |key| + value = delete(key) + self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value + end + self + end + + # Return a new hash with all keys converted to strings. + # This includes the keys from the root hash and from all + # nested hashes. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_stringify_keys + # # => { "person" => { "name" => "Rob", "age" => "28" } } + def deep_stringify_keys + deep_transform_keys{ |key| key.to_s } + end + + # Destructively convert all keys to strings. + # This includes the keys from the root hash and from all + # nested hashes. + def deep_stringify_keys! + deep_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+. This includes the keys from the root hash + # and from all nested hashes. + # + # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } } + # + # hash.deep_symbolize_keys + # # => { person: { name: "Rob", age: "28" } } + def deep_symbolize_keys + deep_transform_keys{ |key| key.to_sym rescue key } + 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! + deep_transform_keys!{ |key| key.to_sym rescue key } + end end diff --git a/activesupport/lib/active_support/core_ext/integer/inflections.rb b/activesupport/lib/active_support/core_ext/integer/inflections.rb index 56f2ed5985..1e30687166 100644 --- a/activesupport/lib/active_support/core_ext/integer/inflections.rb +++ b/activesupport/lib/active_support/core_ext/integer/inflections.rb @@ -10,6 +10,7 @@ class Integer # 1003.ordinalize # => "1003rd" # -11.ordinalize # => "-11th" # -1001.ordinalize # => "-1001st" + # def ordinalize ActiveSupport::Inflector.ordinalize(self) end @@ -23,6 +24,7 @@ class Integer # 1003.ordinal # => "rd" # -11.ordinal # => "th" # -1001.ordinal # => "st" + # def ordinal ActiveSupport::Inflector.ordinal(self) end diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 728069a9a8..ad3f9ebec9 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -65,6 +65,7 @@ module Kernel # # stream = capture(:stdout) { puts 'Cool' } # stream # => "Cool\n" + # def capture(stream) begin stream = stream.to_s @@ -82,6 +83,7 @@ module Kernel # Silences both STDOUT and STDERR, even for subprocesses. # # quietly { system 'bundle install' } + # def quietly silence_stream(STDOUT) do silence_stream(STDERR) do diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb index b0c7b021db..0a9e791030 100644 --- a/activesupport/lib/active_support/core_ext/module/anonymous.rb +++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb @@ -13,6 +13,7 @@ class Module # m = Module.new # creates an anonymous module # M = m # => m gets a name here as a side-effect # m.name # => "M" + # def anonymous? name.nil? end diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index f914425827..b8cb2e347f 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -48,17 +48,17 @@ class Module # # module AppConfiguration # mattr_accessor :google_api_key - # self.google_api_key = "123456789" # - # mattr_accessor :paypal_url - # self.paypal_url = "www.sandbox.paypal.com" + # self.google_api_key = "123456789" # end # + # AppConfiguration.google_api_key # => "123456789" # AppConfiguration.google_api_key = "overriding the api key!" + # AppConfiguration.google_api_key # => "overriding the api key!" # - # To opt out of the instance writer method, pass :instance_writer => false. - # To opt out of the instance reader method, pass :instance_reader => false. - # To opt out of both instance methods, pass :instance_accessor => false. + # To opt out of the instance writer method, pass instance_writer: false. + # To opt out of the instance reader method, pass instance_reader: false. + # To opt out of both instance methods, pass instance_accessor: false. def mattr_accessor(*syms) mattr_reader(*syms) mattr_writer(*syms) diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 30acc87c4a..fbef27c76a 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -100,6 +100,7 @@ class Module # end # # Foo.new.zoo # returns nil + # def delegate(*methods) options = methods.pop unless options.is_a?(Hash) && to = options[:to] diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 649a969149..3c8e811fa4 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -27,6 +27,7 @@ class Module # # M.parent # => Object # Module.new.parent # => Object + # def parent parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object end @@ -43,6 +44,7 @@ class Module # M.parents # => [Object] # M::N.parents # => [M, Object] # X.parents # => [M, Object] + # def parents parents = [] if parent_name diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 09d9af1bde..e238fef5a2 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -43,6 +43,7 @@ class NilClass # +nil+ is blank: # # nil.blank? # => true + # def blank? true end @@ -52,6 +53,7 @@ class FalseClass # +false+ is blank: # # false.blank? # => true + # def blank? true end @@ -61,6 +63,7 @@ class TrueClass # +true+ is not blank: # # true.blank? # => false + # def blank? false end @@ -71,6 +74,7 @@ class Array # # [].blank? # => true # [1,2,3].blank? # => false + # alias_method :blank?, :empty? end @@ -79,6 +83,7 @@ class Hash # # {}.blank? # => true # {:key => 'value'}.blank? # => false + # alias_method :blank?, :empty? end @@ -89,6 +94,7 @@ class String # ' '.blank? # => true # ' '.blank? # => true # ' something here '.blank? # => false + # def blank? self !~ /[^[:space:]]/ end @@ -99,6 +105,7 @@ class Numeric #:nodoc: # # 1.blank? # => false # 0.blank? # => false + # def blank? false end diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb index 883f5f556c..f55fbc282e 100644 --- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/duplicable' + class Object # Returns a deep copy of object if it's duplicable. If it's # not duplicable, returns +self+. diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9cd7485e2e..f1b755c2c4 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -31,6 +31,7 @@ class NilClass # # nil.duplicable? # => false # nil.dup # => TypeError: can't dup NilClass + # def duplicable? false end @@ -41,6 +42,7 @@ class FalseClass # # false.duplicable? # => false # false.dup # => TypeError: can't dup FalseClass + # def duplicable? false end @@ -51,6 +53,7 @@ class TrueClass # # true.duplicable? # => false # true.dup # => TypeError: can't dup TrueClass + # def duplicable? false end @@ -61,6 +64,7 @@ class Symbol # # :my_symbol.duplicable? # => false # :my_symbol.dup # => TypeError: can't dup Symbol + # def duplicable? false end @@ -71,6 +75,7 @@ class Numeric # # 3.duplicable? # => false # 3.dup # => TypeError: can't dup Fixnum + # def duplicable? false 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/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 723bf69189..e058367111 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -36,6 +36,7 @@ class Object # # <tt>with_options</tt> can also be nested since the call is forwarded to its receiver. # Each nesting level will merge inherited defaults in addition to their own. + # def with_options(options) yield ActiveSupport::OptionMerger.new(self, options) end diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 2478f42290..70f2dcb562 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -33,7 +33,7 @@ class String # # => "Once upon a time in a..." # # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...") - # for a total length not exceeding <tt>:length</tt>: + # for a total length not exceeding <tt>length</tt>: # # 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)') # # => "And they f... (continued)" diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index e726a475e0..070bfd7af6 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -5,6 +5,7 @@ require 'active_support/inflector/transliterate' # For instance, you can figure out the name of a table from the name of a class. # # 'ScaleScore'.tableize # => "scale_scores" +# class String # Returns the plural form of the word in the string. # diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index d4e1597840..5226ff0cbe 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -64,6 +64,7 @@ class ERB # in Rails templates: # # <%=j @person.to_json %> + # def json_escape(s) result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] } s.html_safe? ? result.html_safe : result 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/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 20dde73abf..8860636168 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -28,6 +28,7 @@ module ActiveSupport # ActionDispatch::Reloader.to_prepare do # i18n_reloader.execute_if_updated # end + # class FileUpdateChecker # It accepts two parameters on initialization. The first is an array # of files and the second is an optional hash of directories. The hash must diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 3191a3bdc6..6e1c0da991 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -53,6 +53,7 @@ module ActiveSupport # # hash = HashWithIndifferentAccess.new # hash[:key] = "value" + # def []=(key, value) regular_writer(convert_key(key), convert_value(value)) end @@ -68,6 +69,7 @@ module ActiveSupport # hash_2[:key] = "New Value!" # # hash_1.update(hash_2) # => {"key"=>"New Value!"} + # def update(other_hash) if other_hash.is_a? HashWithIndifferentAccess super(other_hash) @@ -85,6 +87,7 @@ module ActiveSupport # hash["key"] = "value" # hash.key? :key # => true # hash.key? "key" # => true + # def key?(key) super(convert_key(key)) end @@ -104,6 +107,7 @@ module ActiveSupport # hash[:a] = "x" # hash[:b] = "y" # hash.values_at("a", "b") # => ["x", "y"] + # def values_at(*indices) indices.collect {|key| self[convert_key(key)]} end @@ -137,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 53a1c3f160..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 @@ -228,6 +245,7 @@ module ActiveSupport # "blargle".safe_constantize # => nil # "UnknownModule".safe_constantize # => nil # "UnknownModule::Foo::Bar".safe_constantize # => nil + # def safe_constantize(camel_cased_word) begin constantize(camel_cased_word) diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index 34450c65d4..c167efc1a7 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -17,6 +17,7 @@ module ActiveSupport # The very last line of +activerecord/lib/active_record/base.rb+ is: # # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) + # @load_hooks = Hash.new { |h,k| h[k] = [] } @loaded = Hash.new { |h,k| h[k] = [] } diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 20ca6ff5f6..d2a6e1bd82 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -111,6 +111,7 @@ module ActiveSupport # option is set to true, it also adds bold to the string. This is based # on the Highline implementation and will automatically append CLEAR to the # end of the returned String. + # def color(text, color, bold=false) return text unless colorize_logging color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 5c7a238934..b65ea6208c 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -31,6 +31,7 @@ module ActiveSupport # powers (it actually does not send anything to your output), and you can collect them # doing @logger.logged(level), where level is the level used in logging, like info, # debug, warn and so on. + # module TestHelper def setup @logger = MockLogger.new @@ -95,6 +96,7 @@ module ActiveSupport # def logger # ActiveRecord::Base.logger = @logger # end + # def set_logger(logger) ActiveSupport::LogSubscriber.logger = logger end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index c002b1c02c..ada2e79ccb 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -36,6 +36,7 @@ module ActiveSupport # Options: # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc' # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+. + # def initialize(secret, options = {}) @secret = secret @cipher = options[:cipher] || 'aes-256-cbc' diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 6d89307638..6735c561d3 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -133,6 +133,7 @@ module ActiveSupport # # Notifications ships with a queue implementation that consumes and publish events # to log subscribers in a thread. You can use any queue implementation you want. + # module Notifications @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) } diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 6bd6f1f2ff..60e6cd55ad 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -13,6 +13,7 @@ # h.girl = 'Mary' # h.boy # => 'John' # h.girl # => 'Mary' +# module ActiveSupport #:nodoc: class OrderedOptions < Hash alias_method :_get, :[] # preserve the original #[] method diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb index e059594bfa..f3f3909a90 100644 --- a/activesupport/lib/active_support/string_inquirer.rb +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -8,6 +8,7 @@ module ActiveSupport # you can call this: # # Rails.env.production? + # class StringInquirer < String def method_missing(method_name, *arguments) if method_name[-1, 1] == "?" diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index f040ee7101..451520ac5c 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -31,6 +31,7 @@ module ActiveSupport # t > Time.utc(1999) # => true # t.is_a?(Time) # => true # t.is_a?(ActiveSupport::TimeWithZone) # => true + # class TimeWithZone # Report class name as 'Time' to thwart type checking @@ -126,6 +127,7 @@ module ActiveSupport # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json # # => "2005/02/01 15:15:10 +0000" + # def as_json(options = nil) if ActiveSupport::JSON::Encoding.use_standard_json_time_format xmlschema 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/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 0bf48dd378..0a1abac767 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -84,6 +84,11 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal 10, (1..4.5).sum assert_equal 6, (1...4).sum assert_equal 'abc', ('a'..'c').sum + assert_equal 50_000_005_000_000, (0..10_000_000).sum + assert_equal 0, (10..0).sum + assert_equal 5, (10..0).sum(5) + assert_equal 10, (10..10).sum + assert_equal 42, (10...10).sum(42) end def test_index_by diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 8239054117..5d422ce5ad 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -4,6 +4,7 @@ require 'bigdecimal' require 'active_support/core_ext/string/access' require 'active_support/ordered_hash' require 'active_support/core_ext/object/conversions' +require 'active_support/core_ext/object/deep_dup' require 'active_support/inflections' class HashExtTest < ActiveSupport::TestCase @@ -24,56 +25,207 @@ 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 } + @nested_illegal_symbols = { [] => { [] => 3} } + @upcase_strings = { 'A' => 1, 'B' => 2 } + @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } } end def test_methods h = {} + assert_respond_to h, :transform_keys + assert_respond_to h, :transform_keys! + assert_respond_to h, :deep_transform_keys + assert_respond_to h, :deep_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_not_mutates + transformed_hash = @mixed.dup + transformed_hash.transform_keys{ |key| key.to_s.upcase } + assert_equal @mixed, transformed_hash + end + + def test_deep_transform_keys + assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase } + end + + def test_deep_transform_keys_not_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_transform_keys{ |key| key.to_s.upcase } + assert_equal @nested_mixed, transformed_hash + 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_transform_keys_with_bang_mutates + transformed_hash = @mixed.dup + transformed_hash.transform_keys!{ |key| key.to_s.upcase } + assert_equal @upcase_strings, transformed_hash + assert_equal @mixed, { :a => 1, "b" => 2 } + end + + def test_deep_transform_keys! + assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } + end + + def test_deep_transform_keys_with_bang_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, transformed_hash + assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } + 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_symbolize_keys_not_mutates + transformed_hash = @mixed.dup + transformed_hash.symbolize_keys + assert_equal @mixed, transformed_hash + 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_deep_symbolize_keys_not_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_symbolize_keys + assert_equal @nested_mixed, transformed_hash + 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_symbolize_keys_with_bang_mutates + transformed_hash = @mixed.dup + transformed_hash.deep_symbolize_keys! + assert_equal @symbols, transformed_hash + assert_equal @mixed, { :a => 1, "b" => 2 } + end + + def test_deep_symbolize_keys! + assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys! + assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys! + assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys! + end + + def test_deep_symbolize_keys_with_bang_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_symbolize_keys! + assert_equal @nested_symbols, transformed_hash + assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } + 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.deep_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.deep_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_stringify_keys_not_mutates + transformed_hash = @mixed.dup + transformed_hash.stringify_keys + assert_equal @mixed, transformed_hash + 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_deep_stringify_keys_not_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_stringify_keys + assert_equal @nested_mixed, transformed_hash + 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_stringify_keys_with_bang_mutates + transformed_hash = @mixed.dup + transformed_hash.stringify_keys! + assert_equal @strings, transformed_hash + assert_equal @mixed, { :a => 1, "b" => 2 } + end + + def test_deep_stringify_keys! + assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys! + end + + def test_deep_stringify_keys_with_bang_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_stringify_keys! + assert_equal @nested_strings, transformed_hash + assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } + 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 +233,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.deep_dup.deep_symbolize_keys! } + assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! } + assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_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.deep_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.deep_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 +280,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 +294,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.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_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 +487,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 +873,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/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 8437ef1347..e5b774425e 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -290,6 +290,10 @@ class StringInflectionsTest < ActiveSupport::TestCase "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10) end + def test_truncate_should_not_be_html_safe + assert !"Hello World!".truncate(12).html_safe? + end + def test_constantize run_constantize_tests_on do |string| string.constantize 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/action_view_overview.textile b/guides/source/action_view_overview.textile index bde30ba21c..fdfa97effa 100644 --- a/guides/source/action_view_overview.textile +++ b/guides/source/action_view_overview.textile @@ -454,7 +454,7 @@ input("post", "title") # => h4. RecordTagHelper -This module provides methods for generating a container tag, such as a +<div>+, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use. +This module provides methods for generating container tags, such as +div+, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use. h5. content_tag_for @@ -542,28 +542,28 @@ image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png h5. register_javascript_expansion -Register one or more JavaScript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register JavaScript files that the plugin installed in +public/javascripts+. +Register one or more JavaScript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register JavaScript files that the plugin installed in +vendor/assets/javascripts+. <ruby> ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"] javascript_include_tag :monkey # => - <script src="/javascripts/head.js"></script> - <script src="/javascripts/body.js"></script> - <script src="/javascripts/tail.js"></script> + <script src="/assets/head.js"></script> + <script src="/assets/body.js"></script> + <script src="/assets/tail.js"></script> </ruby> h5. register_stylesheet_expansion -Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +public/stylesheets+. +Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +vendor/assets/stylesheets+. <ruby> ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] stylesheet_link_tag :monkey # => - <link href="/stylesheets/head.css" media="screen" rel="stylesheet" /> - <link href="/stylesheets/body.css" media="screen" rel="stylesheet" /> - <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" /> + <link href="/assets/head.css" media="screen" rel="stylesheet" /> + <link href="/assets/body.css" media="screen" rel="stylesheet" /> + <link href="/assets/tail.css" media="screen" rel="stylesheet" /> </ruby> h5. auto_discovery_link_tag @@ -577,44 +577,49 @@ auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "RSS h5. image_path -Computes the path to an image asset in the +public/images+ directory. Full paths from the document root will be passed through. Used internally by +image_tag+ to build the image path. +Computes the path to an image asset in the +app/assets/images+ directory. Full paths from the document root will be passed through. Used internally by +image_tag+ to build the image path. <ruby> -image_path("edit.png") # => /images/edit.png +image_path("edit.png") # => /assets/edit.png +</ruby> + +Fingerprint will be added to the filename if config.assets.digest is set to true. + +<ruby> +image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png </ruby> h5. image_url -Computes the url to an image asset in the +public/images+ directory. This will call +image_path+ internally and merge with your current host or your asset host. +Computes the url to an image asset in the +app/asset/images+ directory. This will call +image_path+ internally and merge with your current host or your asset host. <ruby> -image_url("edit.png") # => http://www.example.com/images/edit.png +image_url("edit.png") # => http://www.example.com/assets/edit.png </ruby> h5. image_tag -Returns an html image tag for the source. The source can be a full path or a file that exists in your +public/images+ directory. +Returns an html image tag for the source. The source can be a full path or a file that exists in your +app/assets/images+ directory. <ruby> -image_tag("icon.png") # => <img src="/images/icon.png" alt="Icon" /> +image_tag("icon.png") # => <img src="/assets/icon.png" alt="Icon" /> </ruby> h5. javascript_include_tag -Returns an html script tag for each of the sources provided. You can pass in the filename (+.js+ extension is optional) of JavaScript files that exist in your +public/javascripts+ directory for inclusion into the current page or you can pass the full path relative to your document root. +Returns an html script tag for each of the sources provided. You can pass in the filename (+.js+ extension is optional) of JavaScript files that exist in your +app/assets/javascripts+ directory for inclusion into the current page or you can pass the full path relative to your document root. <ruby> -javascript_include_tag "common" # => - <script src="/javascripts/common.js"></script> +javascript_include_tag "common" # => <script src="/assets/common.js"></script> </ruby> -If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well. +If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +app/assets/javascripts+ directory, it will be included as well. <ruby> javascript_include_tag :defaults </ruby> -You can also include all JavaScript files in the +public/javascripts+ directory using +:all+ as the source. +You can also include all JavaScript files in the +app/assets/javascripts+ directory using +:all+ as the source. <ruby> javascript_include_tag :all @@ -629,18 +634,18 @@ javascript_include_tag :all, :cache => true # => h5. javascript_path -Computes the path to a JavaScript asset in the +public/javascripts+ directory. If the source filename has no extension, +.js+ will be appended. Full paths from the document root will be passed through. Used internally by +javascript_include_tag+ to build the script path. +Computes the path to a JavaScript asset in the +app/assets/javascripts+ directory. If the source filename has no extension, +.js+ will be appended. Full paths from the document root will be passed through. Used internally by +javascript_include_tag+ to build the script path. <ruby> -javascript_path "common" # => /javascripts/common.js +javascript_path "common" # => /assets/common.js </ruby> h5. javascript_url -Computes the url to a JavaScript asset in the +public/javascripts+ directory. This will call +javascript_path+ internally and merge with your current host or your asset host. +Computes the url to a JavaScript asset in the +app/assets/javascripts+ directory. This will call +javascript_path+ internally and merge with your current host or your asset host. <ruby> -javascript_url "common" # => http://www.example.com/javascripts/common.js +javascript_url "common" # => http://www.example.com/assets/common.js </ruby> h5. stylesheet_link_tag @@ -648,8 +653,7 @@ h5. stylesheet_link_tag Returns a stylesheet link tag for the sources specified as arguments. If you don't specify an extension, +.css+ will be appended automatically. <ruby> -stylesheet_link_tag "application" # => - <link href="/stylesheets/application.css" media="screen" rel="stylesheet" /> +stylesheet_link_tag "application" # => <link href="/assets/application.css" media="screen" rel="stylesheet" /> </ruby> You can also include all styles in the stylesheet directory using :all as the source: @@ -662,23 +666,23 @@ You can also cache multiple stylesheets into one file, which requires less HTTP <ruby> stylesheet_link_tag :all, :cache => true - <link href="/stylesheets/all.css" media="screen" rel="stylesheet" /> +# => <link href="/assets/all.css" media="screen" rel="stylesheet" /> </ruby> h5. stylesheet_path -Computes the path to a stylesheet asset in the +public/stylesheets+ directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. +Computes the path to a stylesheet asset in the +app/assets/stylesheets+ directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. <ruby> -stylesheet_path "application" # => /stylesheets/application.css +stylesheet_path "application" # => /assets/application.css </ruby> h5. stylesheet_url -Computes the url to a stylesheet asset in the +public/stylesheets+ directory. This will call +stylesheet_path+ internally and merge with your current host or your asset host. +Computes the url to a stylesheet asset in the +app/assets/stylesheets+ directory. This will call +stylesheet_path+ internally and merge with your current host or your asset host. <ruby> -stylesheet_url "application" # => http://www.example.com/stylesheets/application.css +stylesheet_url "application" # => http://www.example.com/assets/application.css </ruby> h4. AtomFeedHelper diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile index 80faffa49c..587f65529e 100644 --- a/guides/source/active_support_core_extensions.textile +++ b/guides/source/active_support_core_extensions.textile @@ -2549,6 +2549,45 @@ 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. + +Besides that, one can use +deep_transform_keys+ and +deep_transform_keys!+ to perform the block operation on 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_transform_keys{ |key| key.to_s.upcase } +# => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}} +</ruby> + +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 +2618,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 +2659,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/active_support_instrumentation.textile b/guides/source/active_support_instrumentation.textile index 430549fba4..666110495c 100644 --- a/guides/source/active_support_instrumentation.textile +++ b/guides/source/active_support_instrumentation.textile @@ -15,7 +15,7 @@ h3. Introduction to instrumentation The instrumentation API provided by ActiveSupport allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in <TODO: link to section detailing each hook point>. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code. -For example, there is a hook provided within Active Record that is called every time Active Record uses a SQL query on a database. This hook could be *subscribed* to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken. +For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be *subscribed* to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken. You are even able to create your own events inside your application which you can later subscribe to. diff --git a/guides/source/command_line.textile b/guides/source/command_line.textile index b656a0857a..19e42cea93 100644 --- a/guides/source/command_line.textile +++ b/guides/source/command_line.textile @@ -31,20 +31,21 @@ h4. +rails new+ The first thing we'll want to do is create a new Rails application by running the +rails new+ command after installing Rails. -TIP: You can install the rails gem by typing +gem install rails+, if you don't have it already. +INFO: You can install the rails gem by typing +gem install rails+, if you don't have it already. <shell> $ rails new commandsapp create create README.rdoc - create .gitignore create Rakefile create config.ru + create .gitignore create Gemfile create app ... create tmp/cache - create tmp/pids + ... + run bundle install </shell> Rails will set you up with what seems like a huge amount of stuff for such a tiny command! You've got the entire Rails directory structure now with all the code you need to run our simple application right out of the box. @@ -61,17 +62,17 @@ With no further work, +rails server+ will run our new shiny Rails app: $ cd commandsapp $ rails server => Booting WEBrick -=> Rails 3.1.0 application starting in development on http://0.0.0.0:3000 +=> Rails 3.2.3 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server -[2010-04-18 03:20:33] INFO WEBrick 1.3.1 -[2010-04-18 03:20:33] INFO ruby 1.8.7 (2010-01-10) [x86_64-linux] -[2010-04-18 03:20:33] INFO WEBrick::HTTPServer#start: pid=26086 port=3000 +[2012-05-28 00:39:41] INFO WEBrick 1.3.1 +[2012-05-28 00:39:41] INFO ruby 1.9.2 (2011-02-18) [x86_64-darwin11.2.0] +[2012-05-28 00:39:41] INFO WEBrick::HTTPServer#start: pid=69680 port=3000 </shell> With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open "http://localhost:3000":http://localhost:3000, you will see a basic Rails app running. -You can also use the alias "s" to start the server: <tt>rails s</tt>. +INFO: You can also use the alias "s" to start the server: <tt>rails s</tt>. The server can be run on a different port using the +-p+ option. The default development environment can be changed using +-e+. @@ -85,7 +86,7 @@ h4. +rails generate+ The +rails generate+ command uses templates to create a whole lot of things. Running +rails generate+ by itself gives a list of available generators: -You can also use the alias "g" to invoke the generator command: <tt>rails g</tt>. +INFO: You can also use the alias "g" to invoke the generator command: <tt>rails g</tt>. <shell> $ rails generate @@ -97,6 +98,7 @@ Usage: rails generate GENERATOR [args] [options] Please choose a generator below. Rails: + assets controller generator ... @@ -118,23 +120,22 @@ Usage: rails generate controller NAME [action action] [options] ... ... +Description: + ... + + To create a controller within a module, specify the controller name as a + path like 'parent_module/controller_name'. + + ... + Example: - rails generate controller CreditCard open debit credit close + `rails generate controller CreditCard open debit credit close` Credit card controller with URLs like /credit_card/debit. - Controller: app/controllers/credit_card_controller.rb - Views: app/views/credit_card/debit.html.erb [...] - Helper: app/helpers/credit_card_helper.rb - Test: test/functional/credit_card_controller_test.rb - -Modules Example: - rails generate controller 'admin/credit_card' suspend late_fee - - Credit card admin controller with URLs like /admin/credit_card/suspend. - Controller: app/controllers/admin/credit_card_controller.rb - Views: app/views/admin/credit_card/debit.html.erb [...] - Helper: app/helpers/admin/credit_card_helper.rb - Test: test/functional/admin/credit_card_controller_test.rb + Controller: app/controllers/credit_card_controller.rb + Functional Test: test/functional/credit_card_controller_test.rb + Views: app/views/credit_card/debit.html.erb [...] + Helper: app/helpers/credit_card_helper.rb </shell> The controller generator is expecting parameters in the form of +generate controller ControllerName action1 action2+. Let's make a +Greetings+ controller with an action of *hello*, which will say something nice to us. @@ -153,10 +154,10 @@ $ rails generate controller Greetings hello invoke test_unit create test/unit/helpers/greetings_helper_test.rb invoke assets - create app/assets/javascripts/greetings.js - invoke css - create app/assets/stylesheets/greetings.css - + invoke coffee + create app/assets/javascripts/greetings.js.coffee + invoke scss + create app/assets/stylesheets/greetings.css.scss </shell> What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a javascript file and a stylesheet file. @@ -193,21 +194,19 @@ Rails comes with a generator for data models too. <shell> $ rails generate model -Usage: rails generate model NAME [field:type field:type] [options] +Usage: + rails generate model NAME [field[:type][:index] field[:type][:index]] [options] ... -Examples: - rails generate model account - - Model: app/models/account.rb - Test: test/unit/account_test.rb - Fixtures: test/fixtures/accounts.yml - Migration: db/migrate/XXX_add_accounts.rb +ActiveRecord options: + [--migration] # Indicates when to generate migration + # Default: true - rails generate model post title:string body:text published:boolean +... - Creates a Post model with a string title, text body, and published flag. +Description: + Create rails files for model generator. </shell> NOTE: For a list of available field types, refer to the "API documentation":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column for the column method for the +TableDefinition+ class. @@ -218,46 +217,47 @@ We will set up a simple resource called "HighScore" that will keep track of our <shell> $ rails generate scaffold HighScore game:string score:integer - exists app/models/ - exists app/controllers/ - exists app/helpers/ - create app/views/high_scores - create app/views/layouts/ - exists test/functional/ - create test/unit/ - create app/assets/stylesheets/ - create app/views/high_scores/index.html.erb - create app/views/high_scores/show.html.erb - create app/views/high_scores/new.html.erb - create app/views/high_scores/edit.html.erb - create app/views/layouts/high_scores.html.erb - create app/assets/stylesheets/scaffold.css.scss - create app/controllers/high_scores_controller.rb - create test/functional/high_scores_controller_test.rb - create app/helpers/high_scores_helper.rb - route resources :high_scores -dependency model - exists app/models/ - exists test/unit/ - create test/fixtures/ + invoke active_record + create db/migrate/20120528060026_create_high_scores.rb create app/models/high_score.rb - create test/unit/high_score_test.rb - create test/fixtures/high_scores.yml - exists db/migrate - create db/migrate/20100209025147_create_high_scores.rb + invoke test_unit + create test/unit/high_score_test.rb + create test/fixtures/high_scores.yml + route resources :high_scores + invoke scaffold_controller + create app/controllers/high_scores_controller.rb + invoke erb + create app/views/high_scores + create app/views/high_scores/index.html.erb + create app/views/high_scores/edit.html.erb + create app/views/high_scores/show.html.erb + create app/views/high_scores/new.html.erb + create app/views/high_scores/_form.html.erb + invoke test_unit + create test/functional/high_scores_controller_test.rb + invoke helper + create app/helpers/high_scores_helper.rb + invoke test_unit + create test/unit/helpers/high_scores_helper_test.rb + invoke assets + invoke coffee + create app/assets/javascripts/high_scores.js.coffee + invoke scss + create app/assets/stylesheets/high_scores.css.scss + invoke scss + create app/assets/stylesheets/scaffolds.css.scss </shell> The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the +high_scores+ table and fields), takes care of the route for the *resource*, and new tests for everything. -The migration requires that we *migrate*, that is, run some Ruby code (living in that +20100209025147_create_high_scores.rb+) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the +rake db:migrate+ command. We'll talk more about Rake in-depth in a little while. +The migration requires that we *migrate*, that is, run some Ruby code (living in that +20120528060026_create_high_scores.rb+) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the +rake db:migrate+ command. We'll talk more about Rake in-depth in a little while. <shell> $ rake db:migrate -(in /home/foobar/commandsapp) == CreateHighScores: migrating =============================================== -- create_table(:high_scores) - -> 0.0026s -== CreateHighScores: migrated (0.0028s) ====================================== + -> 0.0017s +== CreateHighScores: migrated (0.0019s) ====================================== </shell> INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment. @@ -274,19 +274,19 @@ h4. +rails console+ The +console+ command lets you interact with your Rails application from the command line. On the underside, +rails console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website. -You can also use the alias "c" to invoke the console: <tt>rails c</tt>. +INFO: You can also use the alias "c" to invoke the console: <tt>rails c</tt>. -You can specify the environment in which the +console+ command should operate using the +-e+ switch. +You can specify the environment in which the +console+ command should operate. <shell> -$ rails console -e staging +$ rails console staging </shell> If you wish to test out some code without changing any data, you can do that by invoking +rails console --sandbox+. <shell> $ rails console --sandbox -Loading development environment in sandbox (Rails 3.1.0) +Loading development environment in sandbox (Rails 3.2.3) Any modifications you make will be rolled back on exit irb(main):001:0> </shell> @@ -295,7 +295,7 @@ h4. +rails dbconsole+ +rails dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. -You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>. +INFO: You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>. h4. +rails runner+ @@ -305,7 +305,7 @@ h4. +rails runner+ $ rails runner "Model.long_running_method" </shell> -You can also use the alias "r" to invoke the runner: <tt>rails r</tt>. +INFO: You can also use the alias "r" to invoke the runner: <tt>rails r</tt>. You can specify the environment in which the +runner+ command should operate using the +-e+ switch. @@ -317,31 +317,25 @@ h4. +rails destroy+ Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. -You can also use the alias "d" to invoke the destroy command: <tt>rails d</tt>. +INFO: You can also use the alias "d" to invoke the destroy command: <tt>rails d</tt>. <shell> $ rails generate model Oops - exists app/models/ - exists test/unit/ - exists test/fixtures/ - create app/models/oops.rb - create test/unit/oops_test.rb - create test/fixtures/oops.yml - exists db/migrate - create db/migrate/20081221040817_create_oops.rb + invoke active_record + create db/migrate/20120528062523_create_oops.rb + create app/models/oops.rb + invoke test_unit + create test/unit/oops_test.rb + create test/fixtures/oops.yml +</shell> +<shell> $ rails destroy model Oops - notempty db/migrate - notempty db - rm db/migrate/20081221040817_create_oops.rb - rm test/fixtures/oops.yml - rm test/unit/oops_test.rb - rm app/models/oops.rb - notempty test/fixtures - notempty test - notempty test/unit - notempty test - notempty app/models - notempty app + invoke active_record + remove db/migrate/20120528062523_create_oops.rb + remove app/models/oops.rb + invoke test_unit + remove test/unit/oops_test.rb + remove test/fixtures/oops.yml </shell> h3. Rake @@ -352,16 +346,16 @@ You can get a list of Rake tasks available to you, which will often depend on yo <shell> $ rake --tasks -(in /home/foobar/commandsapp) -rake db:abort_if_pending_migrations # Raises an error if there are pending migrations -rake db:charset # Retrieves the charset for the current environment's database -rake db:collation # Retrieves the collation for the current environment's database -rake db:create # Create the database defined in config/database.yml for the current Rails.env +rake about # List versions of all Rails frameworks and the environment +rake assets:clean # Remove compiled assets +rake assets:precompile # Compile all the assets named in config.assets.precompile +rake db:create # Create the database from config/database.yml for the current Rails.env ... +rake log:clear # Truncates all *.log files in log/ to zero bytes +rake middleware # Prints out your Rack middleware stack ... -rake tmp:pids:clear # Clears all files in tmp/pids -rake tmp:sessions:clear # Clears all files in tmp/sessions -rake tmp:sockets:clear # Clears all files in tmp/sockets +rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear) +rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids </shell> h4. +about+ diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile index f114075cae..af46538bf5 100644 --- a/guides/source/configuring.textile +++ b/guides/source/configuring.textile @@ -585,7 +585,7 @@ After loading the framework and any gems in your application, Rails turns to loa NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down. -TIP: If you have any ordering dependency in your initializers, you can control the load order by naming. For example, +01_critical.rb+ will be loaded before +02_normal.rb+. +TIP: If you have any ordering dependency in your initializers, you can control the load order through naming. Initializer files are loaded in alphabetical order by their path. For example, +01_critical.rb+ will be loaded before +02_normal.rb+. h3. Initialization events diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile index df475a2359..72cdea885f 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 @@ -400,6 +430,35 @@ h4. Iterate as Necessary It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a gem. +h4. Backporting + +Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a rails team member before backporting your changes to avoid wasted effort. + +For simple fixes, the easiest way to backport your change is to "extract a diff from your changes in master and apply them to the target branch":http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git. + +First make sure your changes are the only difference between your current branch and master: + +<shell> +$ git log master..HEAD +</shell> + +Then extract the diff: + +<shell> +$ git format-patch master --stdout > ~/my_changes.patch +</shell> + +Switch over to the target branch and apply your changes: + +<shell> +$ git checkout -b my_backport_branch 3-2-stable +$ git apply ~/my_changes.patch +</shell> + +This works well for simple changes. However, if your changes are complicated or if the code in master has deviated significantly from your target branch, it might require more work on your part. The difficulty of a backport varies greatly from case to case, and sometimes it is simply not worth the effort. + +Once you have resolved all conflicts and made sure all the tests are passing, push your changes and open a separate pull request for your backport. It is also worth noting that older branches might have a different set of build targets than master. When possible, it is best to first test your backport locally against the ruby versions listed in +.travis.yml+ before submitting your pull request. + And then ... think about your next contribution! h3. Rails Contributors 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/debugging_rails_applications.textile b/guides/source/debugging_rails_applications.textile index 45fa4ada78..0802a2db26 100644 --- a/guides/source/debugging_rails_applications.textile +++ b/guides/source/debugging_rails_applications.textile @@ -698,7 +698,7 @@ There are some Rails plugins to help you to find errors and debug your applicati h3. References -* "ruby-debug Homepage":http://www.datanoise.com/ruby-debug +* "ruby-debug Homepage":http://bashdb.sourceforge.net/ruby-debug/home-page.html * "debugger Homepage":http://github.com/cldwalker/debugger * "Article: Debugging a Rails application with ruby-debug":http://www.sitepoint.com/article/debug-rails-app-ruby-debug/ * "ruby-debug Basics screencast":http://brian.maybeyoureinsane.net/blog/2007/05/07/ruby-debug-basics-screencast/ diff --git a/guides/source/engines.textile b/guides/source/engines.textile index c35305a822..86e7254201 100644 --- a/guides/source/engines.textile +++ b/guides/source/engines.textile @@ -36,6 +36,12 @@ To generate an engine with Rails 3.1, you will need to run the plugin generator $ rails plugin new blorgh --full --mountable </shell> +The full list of options for the plugin generator may be seen by typing: + +<shell> +$ rails plugin --help +</shell> + The +--full+ option tells the plugin generator that you want to create an engine (which is a mountable plugin, hence the option name), creating the basic directory structure of an engine by providing things such as the foundations of an +app+ folder, as well a +config/routes.rb+ file. This generator also provides a file at +lib/blorgh/engine.rb+ which is identical in function to an application's +config/application.rb+ file. The +--mountable+ option tells the generator to mount the engine inside the dummy testing application located at +test/dummy+ inside the engine. It does this by placing this line in to the dummy application's +config/routes.rb+ file, located at +test/dummy/config/routes.rb+ inside the engine: 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/getting_started.textile b/guides/source/getting_started.textile index c129aeb2e1..f25e0c0200 100644 --- a/guides/source/getting_started.textile +++ b/guides/source/getting_started.textile @@ -75,7 +75,7 @@ By following along with this guide, you'll create a Rails project called (very) simple weblog. Before you can start building the application, you need to make sure that you have Rails itself installed. -TIP: The examples below use # and $ to denote terminal prompts. If you are using Windows, your prompt will look something like c:\source_code> +TIP: The examples below use # and $ to denote superuser and regular user terminal prompts respectively in a UNIX-like OS. If you are using Windows, your prompt will look something like c:\source_code> h4. Installing Rails @@ -108,7 +108,7 @@ To use this generator, open a terminal, navigate to a directory where you have r $ rails new blog </shell> -This will create a Rails application called Blog in a directory called blog. +This will create a Rails application called Blog in a directory called blog and install the gem dependencies that are already mentioned in +Gemfile+ using +bundle install+. TIP: You can see all of the command line options that the Rails application builder accepts by running +rails new -h+. @@ -138,7 +138,7 @@ application. Most of the work in this tutorial will happen in the +app/+ folder, |README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| |script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html| -|tmp/|Temporary files| +|tmp/|Temporary files (like cache, pid and session files)| |vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems and the Rails source code (if you optionally install it into your project).| h3. Hello, Rails! @@ -177,7 +177,28 @@ To create a new controller, you will need to run the "controller" generator and $ rails generate controller welcome index </shell> -Rails will create several files for you. Most important of these are of course the controller, located at +app/controllers/welcome_controller.rb+ and the view, located at +app/views/welcome/index.html.erb+. +Rails will create several files and a route for you. + +<shell> +create app/controllers/welcome_controller.rb + route get "welcome/index" +invoke erb +create app/views/welcome +create app/views/welcome/index.html.erb +invoke test_unit +create test/functional/welcome_controller_test.rb +invoke helper +create app/helpers/welcome_helper.rb +invoke test_unit +create test/unit/helpers/welcome_helper_test.rb +invoke assets +invoke coffee +create app/assets/javascripts/welcome.js.coffee +invoke scss +create app/assets/stylesheets/welcome.css.scss +</shell> + +Most important of these are of course the controller, located at +app/controllers/welcome_controller.rb+ and the view, located at +app/views/welcome/index.html.erb+. Open the +app/views/welcome/index.html.erb+ file in your text editor and edit it to contain a single line of code: @@ -195,18 +216,27 @@ You need to do this because Rails will serve any static file in the +public+ dir Next, you have to tell Rails where your actual home page is located. -Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +root :to+ and uncomment it. It should look something like the following: +Open the file +config/routes.rb+ in your editor. <ruby> Blog::Application.routes.draw do - - #... + get "welcome/index" + + # 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" + # root :to => "welcome#index" +</ruby> + +This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +root :to+ and uncomment it. It should look something like the following: + +<ruby> +root :to => "welcome#index" </ruby> -The +root :to => "welcome#index"+ tells Rails to map requests to the root of the application to the welcome controller's index action. This was created earlier when you ran the controller generator (+rails generate controller welcome index+). +The +root :to => "welcome#index"+ tells Rails to map requests to the root of the application to the welcome controller's index action and +get "welcome/index"+ tells Rails to map requests to "http://localhost:3000/welcome/index":http://localhost:3000/welcome/index to the welcome controller's index action. This was created earlier when you ran the controller generator (+rails generate controller welcome index+). If you navigate to "http://localhost:3000":http://localhost:3000 in your browser, you'll see the +Hello, Rails!+ message you put into +app/views/welcome/index.html.erb+, indicating that this new route is indeed going to +WelcomeController+'s +index+ action and is rendering the view correctly. @@ -500,7 +530,7 @@ database columns. In the first line we do just that (remember that +params[:post]+ contains the attributes we're interested in). Then, +@post.save+ is responsible for saving the model in the database. Finally, we redirect the user to the +show+ action, -wich we'll define later. +which we'll define later. TIP: As we'll see later, +@post.save+ returns a boolean indicating wherever the model was saved or not. @@ -610,7 +640,7 @@ The +link_to+ method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path for posts. -Let's add links to the other views as well, starting with adding this "New Post" link to +app/views/posts/index.html.erb+, placing it above the +<table>+ tag: +Let's add links to the other views as well, starting with adding this "New Post" link to +app/views/posts/index.html.erb+, placing it above the +<table>+ tag: <erb> <%= link_to 'New post', :action => :new %> @@ -1129,7 +1159,7 @@ together. Here we're using +link_to+ in a different way. We wrap the +:action+ and +:id+ attributes in a hash so that we can pass those two keys in first as one argument, and then the final two keys as another argument. The +:method+ and +:confirm+ -options are used as html5 attributes so that when the click is linked, +options are used as HTML5 attributes so that when the link is clicked, Rails will first show a confirm dialog to the user, and then submit the link with method +delete+. This is done via the JavaScript file +jquery_ujs+ which is automatically included into your application's layout diff --git a/guides/source/initialization.textile b/guides/source/initialization.textile index 155a439e64..48d4373afe 100644 --- a/guides/source/initialization.textile +++ b/guides/source/initialization.textile @@ -1,13 +1,15 @@ h2. The Rails Initialization Process -This guide explains the internals of the initialization process in Rails as of Rails 3.1. It is an extremely in-depth guide and recommended for advanced Rails developers. +This guide explains the internals of the initialization process in Rails +as of Rails 4. It is an extremely in-depth guide and recommended for advanced Rails developers. * Using +rails server+ * Using Passenger endprologue. -This guide goes through every single file, class and method call that is required to boot up the Ruby on Rails stack for a default Rails 3.1 application, explaining each part in detail along the way. For this guide, we will be focusing on how the two most common methods (+rails server+ and Passenger) boot a Rails application. +This guide goes through every single file, class and method call that is +required to boot up the Ruby on Rails stack for a default Rails 4 application, explaining each part in detail along the way. For this guide, we will be focusing on how the two most common methods (+rails server+ and Passenger) boot a Rails application. NOTE: Paths in this guide are relative to Rails or a Rails application unless otherwise specified. @@ -22,16 +24,15 @@ The actual +rails+ command is kept in _bin/rails_: <ruby> #!/usr/bin/env ruby -begin - require "rails/cli" -rescue LoadError - railties_path = File.expand_path('../../railties/lib', __FILE__) +if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git')) + railties_path = File.expand_path('../../lib', __FILE__) $:.unshift(railties_path) - require "rails/cli" end +require "rails/cli" </ruby> -This file will attempt to load +rails/cli+. If it cannot find it then +railties/lib+ is added to the load path (+$:+) before retrying. +This file will first attempt to push the +railties/lib+ directory if +present, and then require +rails/cli+. h4. +railties/lib/rails/cli.rb+ @@ -46,7 +47,7 @@ require 'rails/script_rails_loader' Rails::ScriptRailsLoader.exec_script_rails! require 'rails/ruby_version_check' -Signal.trap("INT") { puts; exit } +Signal.trap("INT") { puts; exit(1) } if ARGV.first == 'plugin' ARGV.shift @@ -120,6 +121,9 @@ exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? This is effectively the same as running +ruby script/rails [arguments]+, where +[arguments]+ at this point in time is simply "server". +TIP: If you execute +script/rails+ directly from your Rails app you will +avoid executing the code that we just described. + h4. +script/rails+ This file is as follows: @@ -134,30 +138,30 @@ The +APP_PATH+ constant will be used later in +rails/commands+. The +config/boot h4. +config/boot.rb+ -+config/boot.rb+ contains this: ++config/boot.rb+ contains: <ruby> # Set up gems listed in the Gemfile. -gemfile = File.expand_path('../../Gemfile', __FILE__) -begin - ENV['BUNDLE_GEMFILE'] = gemfile - require 'bundler' - Bundler.setup -rescue Bundler::GemNotFound => e - STDERR.puts e.message - STDERR.puts "Try running `bundle install`." - exit! -end if File.exist?(gemfile) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) </ruby> -In a standard Rails application, there's a +Gemfile+ which declares all dependencies of the application. +config/boot.rb+ sets +ENV["BUNDLE_GEMFILE"]+ to the location of this file, then requires Bundler and calls +Bundler.setup+ which adds the dependencies of the application (including all the Rails parts) to the load path, making them available for the application to load. The gems that a Rails 3.1 application depends on are as follows: +In a standard Rails application, there's a +Gemfile+ which declares all +dependencies of the application. +config/boot.rb+ sets ++ENV['BUNDLE_GEMFILE']+ to the location of this file. If the Gemfile +exists, +bundler/setup+ is then required. + +The gems that a Rails 4 application depends on are as follows: + +TODO: change these when the Rails 4 release is near. * abstract (1.0.0) -* actionmailer (3.1.0.beta) -* actionpack (3.1.0.beta) -* activemodel (3.1.0.beta) -* activerecord (3.1.0.beta) -* activesupport (3.1.0.beta) +* actionmailer (4.0.0.beta) +* actionpack (4.0.0.beta) +* activemodel (4.0.0.beta) +* activerecord (4.0.0.beta) +* activesupport (4.0.0.beta) * arel (2.0.7) * builder (3.0.0) * bundler (1.0.6) @@ -170,8 +174,8 @@ In a standard Rails application, there's a +Gemfile+ which declares all dependen * rack-cache (0.5.3) * rack-mount (0.6.13) * rack-test (0.5.6) -* rails (3.1.0.beta) -* railties (3.1.0.beta) +* rails (4.0.0.beta) +* railties (4.0.0.beta) * rake (0.8.7) * sqlite3-ruby (1.3.2) * thor (0.14.6) @@ -183,8 +187,11 @@ h4. +rails/commands.rb+ Once +config/boot.rb+ has finished, the next file that is required is +rails/commands+ which will execute a command based on the arguments passed in. In this case, the +ARGV+ array simply contains +server+ which is extracted into the +command+ variable using these lines: <ruby> +ARGV << '--help' if ARGV.empty? + aliases = { "g" => "generate", + "d" => "destroy", "c" => "console", "s" => "server", "db" => "dbconsole", @@ -195,6 +202,9 @@ command = ARGV.shift command = aliases[command] || command </ruby> +TIP: As you can see, an empty ARGV list will make Rails show the help +snippet. + If we used <tt>s</tt> rather than +server+, Rails will use the +aliases+ defined in the file and match them to their respective commands. With the +server+ command, Rails will run this code: <ruby> @@ -361,8 +371,9 @@ This method is defined like this: <ruby> def start + url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" - puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}" + puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}" puts "=> Call with -d to detach" unless options[:daemonize] trap(:INT) { exit } puts "=> Ctrl-C to shutdown server" unless options[:daemonize] @@ -372,6 +383,15 @@ def start FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make)) end + unless options[:daemonize] + wrapped_app # touch the app so the logger is set up + + console = ActiveSupport::Logger.new($stdout) + console.formatter = Rails.logger.formatter + + Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) + end + super ensure # The '-h' option calls exit before @options is set. @@ -380,10 +400,18 @@ ensure end </ruby> -This is where the first output of the Rails initialization happens. This method creates a trap for +INT+ signals, so if you +CTRL+C+ the server, it will exit the process. As we can see from the code here, it will create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+ directories if they don't already exist prior to calling +super+. The +super+ method will call +Rack::Server.start+ which begins its definition like this: +This is where the first output of the Rails initialization happens. This +method creates a trap for +INT+ signals, so if you +CTRL-C+ the server, +it will exit the process. As we can see from the code here, it will +create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+ +directories. It then calls +wrapped_app+ which is responsible for +creating the Rack app, before creating and assignig an +instance of +ActiveSupport::Logger+. + +The +super+ method will call +Rack::Server.start+ which begins its definition like this: <ruby> -def start +def start &blk if options[:warn] $-w = true end @@ -403,22 +431,37 @@ def start pp wrapped_app pp app end -end -</ruby> -In a Rails application, these options are not set at all and therefore aren't used at all. The first line of code that's executed in this method is a call to this method: + check_pid! if options[:pid] -<ruby> -wrapped_app + # Touch the wrapped app, so that the config.ru is loaded before + # daemonization (i.e. before chdir, etc). + wrapped_app + + daemonize_app if options[:daemonize] + + write_pid if options[:pid] + + trap(:INT) do + if server.respond_to?(:shutdown) + server.shutdown + else + exit + end + end + + server.run wrapped_app, options, &blk +end </ruby> -This method calls another method: +The interesting part for a Rails app is the last line, +server.run+. Here we encounter the +wrapped_app+ method again, which this time +we're going to explore more. <ruby> @wrapped_app ||= build_app app </ruby> -Then the +app+ method here is defined like so: +The +app+ method here is defined like so: <ruby> def app @@ -440,7 +483,7 @@ The +options[:config]+ value defaults to +config.ru+ which contains this: # This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) -run YourApp::Application +run <%= app_const %> </ruby> @@ -489,6 +532,7 @@ require "rails" action_controller action_mailer rails/test_unit + sprockets/rails ).each do |framework| begin require "#{framework}/railtie" @@ -501,13 +545,19 @@ First off the line is the +rails+ require itself. h4. +railties/lib/rails.rb+ -This file is responsible for the initial definition of the +Rails+ module and, rather than defining the autoloads like +ActiveSupport+, +ActionDispatch+ and so on, it actually defines other functionality. Such as the +root+, +env+ and +application+ methods which are extremely useful in Rails 3 applications. +This file is responsible for the initial definition of the +Rails+ +module and, rather than defining the autoloads like +ActiveSupport+, ++ActionDispatch+ and so on, it actually defines other functionality. +Such as the +root+, +env+ and +application+ methods which are extremely +useful in Rails 4 applications. However, before all that takes place the +rails/ruby_version_check+ file is required first. h4. +railties/lib/rails/ruby_version_check.rb+ -This file simply checks if the Ruby version is less than 1.8.7 or is 1.9.1 and raises an error if that is the case. Rails 3 simply will not run on earlier versions of Ruby than 1.8.7 or 1.9.1. +This file simply checks if the Ruby version is less than 1.9.3 and +raises an error if that is the case. Rails 4 simply will not run on +earlier versions of Ruby. NOTE: You should always endeavor to run the latest version of Ruby with your Rails applications. The benefits are many, including security fixes and the like, and very often there is a speed increase associated with it. The caveat is that you could have code that potentially breaks on the latest version, which should be fixed to work on the latest version rather than kept around as an excuse not to upgrade. @@ -523,35 +573,28 @@ end These methods can be used to silence STDERR responses and the +silence_stream+ allows you to also silence other streams. Additionally, this mixin allows you to suppress exceptions and capture streams. For more information see the "Silencing Warnings, Streams, and Exceptions":active_support_core_extensions.html#silencing-warnings-streams-and-exceptions section from the Active Support Core Extensions Guide. -h4. +active_support/core_ext/logger.rb+ - -The next file that is required is another Active Support core extension, this time to the +Logger+ class. This begins by defining the +around_[level]+ helpers for the +Logger+ class as well as other methods such as a +datetime_format+ getter and setter for the +formatter+ object tied to a +Logger+ object. +h4. +active_support/core_ext/array/extract_options.rb+ -For more information see the "Extensions to Logger":active_support_core_extensions.html#extensions-to-logger section from the Active Support Core Extensions Guide. +The next file that is required is another Active Support core extension, +this time to the +Array+ and +Hash+ classes. This file defines an ++extract_options!+ method which Rails uses to extract options from +parameters. h4. +railties/lib/rails/application.rb+ -The next file required by +railties/lib/rails.rb+ is +application.rb+. This file defines the +Rails::Application+ constant which the application's class defined in +config/application.rb+ in a standard Rails application depends on. Before the +Rails::Application+ class is defined however, there's some other files that get required first. - -The first of these is +active_support/core_ext/hash/reverse_merge+ which can be "read about in the Active Support Core Extensions guide":active_support_core_extensions.html#merging under the "Merging" section. - -h4. +active_support/file_update_checker.rb+ - -The +ActiveSupport::FileUpdateChecker+ class defined within this file is responsible for checking if a file has been updated since it was last checked. This is used for monitoring the routes file for changes during development environment runs. +The next file required by +railties/lib/rails.rb+ is +application.rb+. +This file defines the +Rails::Application+ constant which the +application's class defined in +config/application.rb+ in a standard +Rails application depends on. -h4. +railties/lib/rails/plugin.rb+ +Before the +Rails::Application+ class is +defined however, +rails/engine+ is also loaded, which is responsible for +handling the behavior and definitions of Rails engines. -This file defines +Rails::Plugin+ which inherits from +Rails::Engine+. Unlike +Rails::Engine+ and +Rails::Railtie+ however, this class is not designed to be inherited from. Instead, this is used simply for loading plugins from within an application and an engine. +TIP: You can read more about engines in the "Getting Started with Engines":engines.html guide. -This file begins by requiring +rails/engine.rb+ - -h4. +railties/lib/rails/engine.rb+ - -The +rails/engine.rb+ file defines the +Rails::Engine+ class which inherits from +Rails::Railtie+. The +Rails::Engine+ class defines much of the functionality found within a standard application class such as the +routes+ and +config+ methods. - -The "API documentation":http://api.rubyonrails.org/classes/Rails/Engine.html for +Rails::Engine+ explains the function of this class pretty well. - -This file's first line requires +rails/railtie.rb+. +Among other things, Rails Engine is also responsible for loading the +Railtie class. h4. +railties/lib/rails/railtie.rb+ @@ -613,7 +656,7 @@ h4. +activesupport/lib/active_support/deprecation/proxy_wrappers.rb+ +proxy_wrappers.rb+ defines deprecation wrappers for methods, instance variables and constants. Previously, this was used for the +RAILS_ENV+ and +RAILS_ROOT+ constants for 3.0 but since then these constants have been removed. The deprecation message that would be raised from these would be something like: <plain> - BadConstant is deprecated! Use GoodConstant instead. +BadConstant is deprecated! Use GoodConstant instead. </plain> h4. +active_support/ordered_options+ @@ -622,7 +665,30 @@ This file is the next file required from +rails/configuration.rb+ is the file th The next file required is +active_support/core_ext/hash/deep_dup+ which is covered in "Active Support Core Extensions guide":active_support_core_extensions.html#deep_dup -The file that is required next from is +rails/paths+ +h4. +active_support/core_ext/object+ + +This file is responsible for requiring many more Active Support core extensions: + +<ruby> +require 'active_support/core_ext/object/acts_like' +require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/object/deep_dup' +require 'active_support/core_ext/object/try' +require 'active_support/core_ext/object/inclusion' + +require 'active_support/core_ext/object/conversions' +require 'active_support/core_ext/object/instance_variables' + +require 'active_support/core_ext/object/to_json' +require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/object/to_query' +require 'active_support/core_ext/object/with_options' +</ruby> + +The Rails API documentation covers them in great detail, so we're not going to explain each of them. + +The file that is required next from +rails/configuration+ is +rails/paths+. h4. +railties/lib/rails/paths.rb+ @@ -638,7 +704,6 @@ module Rails autoload :Debugger, "rails/rack/debugger" autoload :Logger, "rails/rack/logger" autoload :LogTailer, "rails/rack/log_tailer" - autoload :Static, "rails/rack/static" end end </ruby> @@ -664,9 +729,23 @@ h4. +active_support/inflections+ This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes". +<ruby> +inflect.irregular('zombie', 'zombies') +</ruby> + h4. +activesupport/lib/active_support/inflector/transliterate.rb+ -In this file is where the "+transliterate+":http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate and +parameterize+:http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize methods are defined. The documentation for both of these methods is very much worth reading. +This is the file that defines the "+transliterate+":http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate and "+parameterize+":http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize methods. + +h4. +active_support/core_ext/module/introspection+ + +The next file loaded by +rails/railtie+ is the introspection core +extension, which extends +Module+ with methods like +parent_name+, +parent+ and ++parents+. + +h4. +active_support/core_ext/module/delegation+ + +The final file loaded by +rails/railtie+ is the delegation core extension, which defines the "+delegate+":http://api.rubyonrails.org/classes/Module.html#method-i-delegate method. h4. Back to +railties/lib/rails/railtie.rb+ @@ -846,7 +925,7 @@ The +initializers_chain+ method referenced in the +initializers_for+ method is d <ruby> def initializers_chain initializers = Collection.new - ancestors.reverse_each do | klass | + ancestors.reverse_each do |klass| next unless klass.respond_to?(:initializers) initializers = initializers + klass.initializers end @@ -909,46 +988,35 @@ This file defines the +ActiveSupport::Railtie+ constant which like the +I18n::Ra Then this Railtie sets up three more initializers: -* +active_support.initialize_whiny_nils+ * +active_support.deprecation_behavior+ * +active_support.initialize_time_zone+ +* +active_support.set_configs+ We will cover what each of these initializers do when they run. Once the +active_support/railtie+ file has finished loading the next file required from +railties/lib/rails.rb+ is the +action_dispatch/railtie+. -h4. +activesupport/lib/action_dispatch/railtie.rb+ +h4. +actionpack/lib/action_dispatch/railtie.rb+ This file defines the +ActionDispatch::Railtie+ class, but not before requiring +action_dispatch+. -h4. +activesupport/lib/action_dispatch.rb+ - -This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are. - -<ruby> -activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) -$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) - -activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) -$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) -</ruby> - -In effect, these lines only define the +activesupport_path+ and +activemodel_path+ variables and nothing more. +h4. +actionpack/lib/action_dispatch.rb+ -The next two requires in this file are already done, so they are not run: +This file starts off with the following requires: <ruby> require 'active_support' require 'active_support/dependencies/autoload' +require 'active_support/core_ext/module/attribute_accessors' </ruby> -The following require is to +action_pack+ (+activesupport/lib/action_pack.rb+) which has a 22-line copyright notice at the top of it and ends in a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant: +The following require is to +action_pack+ (+actionpack/lib/action_pack.rb+) which contains a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant: <ruby> module ActionPack module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 + MAJOR = 4 + MINOR = 0 TINY = 0 PRE = "beta" @@ -966,8 +1034,8 @@ This file makes a require to +active_model/version+ which defines the version fo <ruby> module ActiveModel module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 + MAJOR = 4 + MINOR = 0 TINY = 0 PRE = "beta" @@ -1004,7 +1072,7 @@ Once it has finished loading, the +I18n.load_path+ method is used to add the +ac The loading of this file finishes the loading of +active_model+ and so we go back to +action_dispatch+. -h4. Back to +activesupport/lib/action_dispatch.rb+ +h4. Back to +actionpack/lib/action_dispatch.rb+ The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here: @@ -1018,7 +1086,7 @@ The +Assertions+ module is in the +action_dispatch/testing+ folder rather than s Finally, this file defines a top-level autoload, the +Mime+ constant. -h4. Back to +activesupport/lib/action_dispatch/railtie.rb+ +h4. Back to +actionpack/lib/action_dispatch/railtie.rb+ After +action_dispatch+ is required in this file, the +ActionDispatch::Railtie+ class is defined and is yet another class that inherits from +Rails::Railtie+. This class defines some initial configuration option defaults for +config.action_dispatch+ before setting up a single initializer called +action_dispatch.configure+. @@ -1040,22 +1108,21 @@ h4. +activerecord/lib/active_record.rb+ This file begins by detecting if the +lib+ directories of +active_support+ and +active_model+ are not in the load path and if they aren't then adds them. As we saw back in +action_dispatch.rb+, these directories are already there. -The first three requires have already been done by other files and so aren't loaded here, but the 4th require, the one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module. +The first couple of requires have already been done by other files and so aren't loaded here, but the next one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module. <ruby> require 'active_support' -require 'active_support/i18n' require 'active_model' require 'arel' </ruby> -The 5th require in this file is one to +active_record/version+ which defines the +ActiveRecord::VERSION+ constant: +The file required next is +active_record/version+ which defines the +ActiveRecord::VERSION+ constant: <ruby> module ActiveRecord module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 + MAJOR = 4 + MINOR = 0 TINY = 0 PRE = "beta" @@ -1079,7 +1146,9 @@ This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+. The file then finishes with this line: <ruby> -I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' +ActiveSupport.on_load(:i18n) do + I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' +end </ruby> This will add the translations from +activerecord/lib/active_record/locale/en.yml+ to the load path for +I18n+, with this file being parsed when all the translations are loaded. diff --git a/guides/source/rails_on_rack.textile b/guides/source/rails_on_rack.textile index d8910cf1d0..3a7c392508 100644 --- a/guides/source/rails_on_rack.textile +++ b/guides/source/rails_on_rack.textile @@ -23,29 +23,49 @@ h3. Rails on Rack h4. Rails Application's Rack Object -<tt>ActionController::Dispatcher.new</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ActionController::Dispatcher.new+ object to serve a Rails application. +<tt>ApplicationName::Application</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ApplicationName::Application+ object to serve a Rails application. h4. +rails server+ -<tt>rails server</tt> does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails' equivalent of Rack's +rackup+ script. +<tt>rails server</tt> does the basic job of creating a +Rack::Server+ object and starting the webserver. -Here's how +rails server+ creates an instance of +Rack::Builder+ +Here's how +rails server+ creates an instance of +Rack::Server+ <ruby> -app = Rack::Builder.new { - use Rails::Rack::LogTailer unless options[:detach] - use Rails::Rack::Debugger if options[:debugger] - use ActionDispatch::Static - run ActionController::Dispatcher.new -}.to_app +Rails::Server.new.tap { |server| + require APP_PATH + Dir.chdir(Rails.application.root) + server.start +} </ruby> -Middlewares used in the code above are primarily useful only in the development environment. The following table explains their usage: +The +Rails::Server+ inherits from +Rack::Server+ and calls the +Rack::Server#start+ method this way: + +<ruby> +class Server < ::Rack::Server + def start + ... + super + end +end +</ruby> + +Here's how it loads the middlewares: + +<ruby> +def middleware + middlewares = [] + middlewares << [Rails::Rack::Debugger] if options[:debugger] + middlewares << [::Rack::ContentLength] + Hash.new(middlewares) +end +</ruby> + ++Rails::Rack::Debugger+ is primarily useful only in the development environment. The following table explains the usage of the loaded middlewares: |_.Middleware|_.Purpose| -|+Rails::Rack::LogTailer+|Appends log file output to console| -|+ActionDispatch::Static+|Serves static files inside +Rails.root/public+ directory| |+Rails::Rack::Debugger+|Starts Debugger| +|+Rack::ContentLength+|Counts the number of bytes in the response and set the HTTP Content-Length header| h4. +rackup+ @@ -55,9 +75,9 @@ To use +rackup+ instead of Rails' +rails server+, you can put the following insi # Rails.root/config.ru require "config/environment" -use Rails::Rack::LogTailer -use ActionDispatch::Static -run ActionController::Dispatcher.new +use Rack::Debugger +use Rack::ContentLength +run ApplicationName::Application </ruby> And start the server: @@ -72,11 +92,11 @@ To find out more about different +rackup+ options: $ rackup --help </shell> -h3. Action Controller Middleware Stack +h3. Action Dispatcher Middleware Stack -Many of Action Controller's internal components are implemented as Rack middlewares. +ActionController::Dispatcher+ uses +ActionController::MiddlewareStack+ to combine various internal and external middlewares to form a complete Rails Rack application. +Many of Action Dispatchers's internal components are implemented as Rack middlewares. +Rails::Application+ uses +ActionDispatch::MiddlewareStack+ to combine various internal and external middlewares to form a complete Rails Rack application. -NOTE: +ActionController::MiddlewareStack+ is Rails' equivalent of +Rack::Builder+, but built for better flexibility and more features to meet Rails' requirements. +NOTE: +ActionDispatch::MiddlewareStack+ is Rails' equivalent of +Rack::Builder+, but built for better flexibility and more features to meet Rails' requirements. h4. Inspecting Middleware Stack @@ -111,7 +131,7 @@ use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport -run Blog::Application.routes +run ApplicationName::Application.routes </ruby> Purpose of each of this middlewares is explained in the "Internal Middlewares":#internal-middleware-stack section. @@ -172,7 +192,7 @@ use ActionDispatch::Static use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8> use Rack::Runtime ... -run Myapp::Application.routes +run Blog::Application.routes </shell> h4. Internal Middleware Stack @@ -264,7 +284,7 @@ config.middleware.clear <ruby> # config.ru use MyOwnStackFromScratch -run ActionController::Dispatcher.new +run ApplicationName::Application </ruby> h3. Resources diff --git a/guides/source/routing.textile b/guides/source/routing.textile index 4a50edbb15..0773a96c67 100644 --- a/guides/source/routing.textile +++ b/guides/source/routing.textile @@ -395,7 +395,7 @@ NOTE: You can't use +:namespace+ or +:module+ with a +:controller+ path segment. get ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/ </ruby> -TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash. +TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment, add a constraint that overrides this – for example, +:id+ => /[^\/]+/ allows anything except a slash. h4. Static Segments @@ -445,6 +445,14 @@ get 'exit' => 'sessions#destroy', :as => :logout This will create +logout_path+ and +logout_url+ as named helpers in your application. Calling +logout_path+ will return +/exit+ +You can also use this to override routing methods defined by resources, like this: + +<ruby> +get ':username', :to => "users#show", :as => :user +</ruby> + +This will define a +user_path+ method that will be available in controllers, helpers and views that will go to a route such as +/bob+. Inside the +show+ action of +UsersController+, +params[:username]+ will contain the username for the user. Change +:username+ in the route definition if you do not want your parameter name to be +:username+. + h4. HTTP Verb Constraints In general, you should use the +get+, +post+, +put+ and +delete+ methods to constrain a route to a particular verb. You can use the +match+ method with the +:via+ option to match multiple verbs at once: diff --git a/guides/source/security.textile b/guides/source/security.textile index ac55d60368..0931dd6393 100644 --- a/guides/source/security.textile +++ b/guides/source/security.textile @@ -30,7 +30,7 @@ A good place to start looking at security is with sessions, which can be vulnera h4. What are Sessions? --- _HTTP is a stateless protocol. Sessions make it stateful._ +NOTE: _HTTP is a stateless protocol. Sessions make it stateful._ Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request. Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application. @@ -44,13 +44,13 @@ User.find(session[:user_id]) h4. Session id --- _The session id is a 32 byte long MD5 hash value._ +NOTE: _The session id is a 32 byte long MD5 hash value._ A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date. h4. Session Hijacking --- _Stealing a user's session id lets an attacker use the web application in the victim's name._ +WARNING: _Stealing a user's session id lets an attacker use the web application in the victim's name._ Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session. @@ -72,7 +72,7 @@ The main objective of most attackers is to make money. The underground prices fo h4. Session Guidelines --- _Here are some general guidelines on sessions._ +Here are some general guidelines on sessions. * _(highlight)Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below). This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate. @@ -81,7 +81,7 @@ This will also be a good idea, if you modify the structure of an object and old h4. Session Storage --- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecord::SessionStore and ActionDispatch::Session::CookieStore._ +NOTE: _Rails provides several storage mechanisms for the session hashes. The most important are +ActiveRecord::SessionStore+ and +ActionDispatch::Session::CookieStore+._ There are a number of session storages, i.e. where Rails saves the session hash and session id. Most real-live applications choose ActiveRecord::SessionStore (or one of its derivatives) over file storage due to performance and maintenance reasons. ActiveRecord::SessionStore keeps the session id and hash in a database table and saves and retrieves the hash on every request. @@ -104,7 +104,7 @@ There are, however, derivatives of CookieStore which encrypt the session hash, s h4. Replay Attacks for CookieStore Sessions --- _Another sort of attack you have to be aware of when using CookieStore is the replay attack._ +TIP: _Another sort of attack you have to be aware of when using +CookieStore+ is the replay attack._ It works like this: @@ -120,7 +120,7 @@ The best _(highlight)solution against it is not to store this kind of data in a h4. Session Fixation --- _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._ +NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._ !images/session_fixation.png(Session fixation)! @@ -135,7 +135,7 @@ This attack focuses on fixing a user's session id known to the attacker, and for h4. Session Fixation – Countermeasures --- _One line of code will protect you from session fixation._ +TIP: _One line of code will protect you from session fixation._ The most effective countermeasure is to _(highlight)issue a new session identifier_ and declare the old one invalid after a successful login. That way, an attacker cannot use the fixed session identifier. This is a good countermeasure against session hijacking, as well. Here is how to create a new session in Rails: @@ -149,7 +149,7 @@ Another countermeasure is to _(highlight)save user-specific properties in the se h4. Session Expiry --- _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._ +NOTE: _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._ One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _(highlight)expire sessions in a database table_. Call +Session.sweep("20 minutes")+ to expire sessions that were used longer than 20 minutes ago. @@ -174,7 +174,7 @@ delete_all "updated_at < '#{time.ago.to_s(:db)}' OR h3. Cross-Site Request Forgery (CSRF) --- _This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands._ +This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands. !images/csrf.png! @@ -193,7 +193,7 @@ CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) -- less t h4. CSRF Countermeasures --- _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._ +NOTE: _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._ The HTTP protocol basically provides two main types of requests - GET and POST (and more, but they are not supported by most browsers). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST: @@ -236,6 +236,17 @@ protect_from_forgery :secret => "123456789012345678901234567890..." This will automatically include a security token, calculated from the current session and the server-side secret, in all forms and Ajax requests generated by Rails. You won't need the secret, if you use CookieStorage as session storage. If the security token doesn't match what was expected, the session will be reset. *Note:* In Rails versions prior to 3.0.4, this raised an <tt>ActionController::InvalidAuthenticityToken</tt> error. +It is common to use persistent cookies to store user information, with +cookies.permanent+ for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself: + +<ruby> +def handle_unverified_request + super + sign_out_user # Example method that will destroy the user cookies. +end +</ruby> + +The above method can be placed in the +ApplicationController+ and will be called when a CSRF token is not present on a non-GET request. + Note that _(highlight)cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so he can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later. h3. Redirection and Files @@ -244,7 +255,7 @@ Another class of security vulnerabilities surrounds the use of redirection and f h4. Redirection --- _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._ +WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._ Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to= www.attacker.com. Here is an example of a legacy action: @@ -272,7 +283,7 @@ This example is a Base64 encoded JavaScript which displays a simple message box. h4. File Uploads --- _Make sure file uploads don't overwrite important files, and process media files asynchronously._ +NOTE: _Make sure file uploads don't overwrite important files, and process media files asynchronously._ Many web applications allow users to upload files. _(highlight)File names, which the user may choose (partly), should always be filtered_ as an attacker could use a malicious file name to overwrite any file on the server. If you store file uploads at /var/www/uploads, and the user enters a file name like “../../../etc/passwd”, it may overwrite an important file. Of course, the Ruby interpreter would need the appropriate permissions to do so – one more reason to run web servers, database servers and other programs as a less privileged Unix user. @@ -297,7 +308,7 @@ The solution to this is best to _(highlight)process media files asynchronously_: h4. Executable Code in File Uploads --- _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._ +WARNING: _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._ The popular Apache web server has an option called DocumentRoot. This is the home directory of the web site, everything in this directory tree will be served by the web server. If there are files with a certain file name extension, the code in it will be executed when requested (might require some options to be set). Examples for this are PHP and CGI files. Now think of a situation where an attacker uploads a file “file.cgi” with code in it, which will be executed when someone downloads the file. @@ -305,7 +316,7 @@ _(highlight)If your Apache DocumentRoot points to Rails' /public directory, do n h4. File Downloads --- _Make sure users cannot download arbitrary files._ +NOTE: _Make sure users cannot download arbitrary files._ Just as you have to filter file names for uploads, you have to do so for downloads. The send_file() method sends files from the server to the client. If you use a file name, that the user entered, without filtering, any file can be downloaded: @@ -327,7 +338,7 @@ Another (additional) approach is to store the file names in the database and nam h3. Intranet and Admin Security --- _Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world._ +Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world. In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
@@ -359,7 +370,7 @@ The common admin interface works like this: it's located at www.example.com/admi h3. Mass Assignment --- _Without any precautions Model.new(params[:model]) allows attackers to set any database column's value._ +WARNING: _Without any precautions +Model.new(params[:model]+) allows attackers to set any database column's value._ The mass-assignment feature may become a problem, as it allows an attacker to set any model's attributes by manipulating the hash passed to a model's +new()+ method: @@ -471,7 +482,7 @@ This will create an empty whitelist of attributes available for mass-assignment h3. User Management --- _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._ +NOTE: _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._ There are a number of authentication plug-ins for Rails available. Good ones, such as the popular "devise":https://github.com/plataformatec/devise and "authlogic":https://github.com/binarylogic/authlogic, store only encrypted passwords, not plain-text passwords. In Rails 3.1 you can use the built-in +has_secure_password+ method which has similar features. @@ -498,7 +509,7 @@ And thus it found the first user in the database, returned it and logged him in. h4. Brute-Forcing Accounts --- _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._ +NOTE: _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._ A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user names and a dictionary, an automatic program may find the correct password in a matter of minutes. @@ -510,7 +521,7 @@ In order to mitigate such attacks, _(highlight)display a generic error message o h4. Account Hijacking --- _Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?_ +Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?. h5. Passwords @@ -526,7 +537,7 @@ Depending on your web application, there may be more ways to hijack the user's a h4. CAPTCHAs --- _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._ +INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._ But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is "reCAPTCHA":http://recaptcha.net/ which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. "ReCAPTCHA":http://ambethia.com/recaptcha/ is also a Rails plug-in with the same name as the API. @@ -553,7 +564,7 @@ Note that this protects you only from automatic bots, targeted tailor-made bots h4. Logging --- _Tell Rails not to put passwords in the log files._ +WARNING: _Tell Rails not to put passwords in the log files._ By default, Rails logs all requests being made to the web application. But log files can be a huge security issue, as they may contain login credentials, credit card numbers et cetera. When designing a web application security concept, you should also think about what will happen if an attacker got (full) access to the web server. Encrypting secrets and passwords in the database will be quite useless, if the log files list them in clear text. You can _(highlight)filter certain request parameters from your log files_ by appending them to <tt>config.filter_parameters</tt> in the application configuration. These parameters will be marked [FILTERED] in the log. @@ -563,7 +574,7 @@ config.filter_parameters << :password h4. Good Passwords --- _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._ +INFO: _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._ Bruce Schneier, a security technologist, "has analyzed":http://www.schneier.com/blog/archives/2006/12/realworld_passw.html 34,000 real-world user names and passwords from the MySpace phishing attack mentioned <a href="#examples-from-the-underground">below</a>. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are: @@ -575,7 +586,7 @@ A good password is a long alphanumeric combination of mixed cases. As this is qu h4. Regular Expressions --- _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._ +INFO: _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._ Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books make this wrong. So how is this a security threat? Imagine you have a File model and you validate the file name by a regular expression like this: @@ -599,7 +610,7 @@ Whereas %0A is a line feed in URL encoding, so Rails automatically converts it t h4. Privilege Escalation --- _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._ +WARNING: _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._ The most common parameter that a user might tamper with, is the id parameter, as in +http://www.domain.com/project/1+, whereas 1 is the id. It will be available in params in the controller. There, you will most likely do something like this: @@ -619,13 +630,13 @@ Don't be fooled by security by obfuscation and JavaScript security. The Web Deve h3. Injection --- _Injection is a class of attacks that introduce malicious code or parameters into a web application in order to run it within its security context. Prominent examples of injection are cross-site scripting (XSS) and SQL injection._ +INFO: _Injection is a class of attacks that introduce malicious code or parameters into a web application in order to run it within its security context. Prominent examples of injection are cross-site scripting (XSS) and SQL injection._ Injection is very tricky, because the same code or parameter can be malicious in one context, but totally harmless in another. A context can be a scripting, query or programming language, the shell or a Ruby/Rails method. The following sections will cover all important contexts where injection attacks may happen. The first section, however, covers an architectural decision in connection with Injection. h4. Whitelists versus Blacklists --- _When sanitizing, protecting or verifying something, whitelists over blacklists._ +NOTE: _When sanitizing, protecting or verifying something, whitelists over blacklists._ A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _(highlight)prefer to use whitelist approaches_: @@ -640,7 +651,7 @@ Whitelists are also a good approach against the human factor of forgetting somet h4. SQL Injection --- _Thanks to clever methods, this is hardly a problem in most Rails applications. However, this is a very devastating and common attack in web applications, so it is important to understand the problem._ +INFO: _Thanks to clever methods, this is hardly a problem in most Rails applications. However, this is a very devastating and common attack in web applications, so it is important to understand the problem._ h5(#sql-injection-introduction). Introduction @@ -719,7 +730,7 @@ The array or hash form is only available in model instances. You can try +saniti h4. Cross-Site Scripting (XSS) --- _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._ +INFO: _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._ h5. Entry Points @@ -847,7 +858,7 @@ The MySpace Samy worm will be discussed in the CSS Injection section. h4. CSS Injection --- _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._ +INFO: _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._ CSS Injection is explained best by a well-known worm, the "MySpace Samy worm":http://namb.la/popular/tech.html. This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, but it creates too much traffic on MySpace, so that the site goes offline. The following is a technical explanation of the worm. @@ -887,7 +898,7 @@ This example, again, showed that a blacklist filter is never complete. However, h4. Textile Injection --- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._ +If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS. For example, RedCloth translates +_test_+ to <em>test<em>, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the "all-new version 4":http://www.redcloth.org that removed serious bugs. However, even that version has "some security bugs":http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html, so the countermeasures still apply. Here is an example for version 3.0.4: @@ -916,13 +927,13 @@ It is recommended to _(highlight)use RedCloth in combination with a whitelist in h4. Ajax Injection --- _The same security precautions have to be taken for Ajax actions as for “normal” ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._ +NOTE: _The same security precautions have to be taken for Ajax actions as for “normal” ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._ If you use the "in_place_editor plugin":http://dev.rubyonrails.org/browser/plugins/in_place_editing, or actions that return a string, rather than rendering a view, _(highlight)you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method. h4. Command Line Injection --- _Use user-supplied command line parameters with caution._ +NOTE: _Use user-supplied command line parameters with caution._ If your application has to execute commands in the underlying operating system, there are several methods in Ruby: exec(command), syscall(command), system(command) and `command`. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|). @@ -936,7 +947,7 @@ system("/bin/echo","hello; rm *") h4. Header Injection --- _HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting._ +WARNING: _HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting._ HTTP request headers have a Referer, User-Agent (client software), and Cookie field, among others. Response headers for example have a status code, Cookie and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. _(highlight)Remember to escape these header fields, too._ For example when you display the user agent in an administration area. 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/CHANGELOG.md b/railties/CHANGELOG.md index ccccc178c5..787bafea04 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* Add `/rails/info/routes` path, displays same information as `rake routes` *Richard Schneeman & Andrew White* + * Improved `rake routes` output for redirects *Łukasz Strzałkowski & Andrew White* * Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki* diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 6cd2c425c3..660e864d2a 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -48,6 +48,7 @@ module Rails # 10) Build the middleware stack and run to_prepare callbacks # 11) Run config.before_eager_load and eager_load if cache classes is true # 12) Run config.after_initialize callbacks + # class Application < Engine autoload :Bootstrap, 'rails/application/bootstrap' autoload :Configuration, 'rails/application/configuration' @@ -74,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, @@ -92,7 +97,7 @@ module Rails # Rails application, you will need to add lib to $LOAD_PATH on your own in case # you need to load files in lib/ during the application configuration as well. def add_lib_to_load_path! #:nodoc: - path = config.root.join('lib').to_s + path = File.join config.root, 'lib' $LOAD_PATH.unshift(path) if File.exists?(path) end @@ -180,7 +185,7 @@ module Rails end all = (railties.all - order) - all.push(self) unless all.include?(self) + all.push(self) unless (all + order).include?(self) order.push(:all) unless order.include?(:all) index = order.index(:all) diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 84f2601f28..60aa40b92f 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -23,6 +23,8 @@ module Rails if Rails.env.development? app.routes.append do get '/rails/info/properties' => "rails/info#properties" + get '/rails/info/routes' => "rails/info#routes" + get '/rails/info' => "rails/info#index" end end end diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/route_inspector.rb index b23fb3e920..942c4f4789 100644 --- a/railties/lib/rails/application/route_inspector.rb +++ b/railties/lib/rails/application/route_inspector.rb @@ -51,7 +51,7 @@ module Rails end def internal? - path =~ %r{/rails/info/properties|^#{Rails.application.config.assets.prefix}} + path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}} end def engine? 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/commands/server.rb b/railties/lib/rails/commands/server.rb index 4c4caad69f..e68d2e05c5 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -69,7 +69,7 @@ module Rails #Create required tmp directories if not found %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make)) + FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) end unless options[:daemonize] diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 57168eee83..5fa7f043c6 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -32,31 +32,32 @@ module Rails # And finally they can also be removed from the stack completely: # # config.middleware.delete ActionDispatch::BestStandardsSupport + # class MiddlewareStackProxy def initialize @operations = [] 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..4c7199a2e2 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) } @@ -404,9 +416,9 @@ module Rails # Finds engine with given path def find(path) - expanded_path = File.expand_path path.to_s + expanded_path = File.expand_path path Rails::Engine::Railties.engines.find { |engine| - File.expand_path(engine.root.to_s) == expanded_path + File.expand_path(engine.root) == expanded_path } end end @@ -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 f4de5a4375..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 @@ -28,6 +28,7 @@ module Rails # If you want to disable color in console, do: # # config.generators.colorize_logging = false + # def generators #:nodoc: @generators ||= Rails::Configuration::Generators.new yield(@generators) if block_given? diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 6cd2ea2bbd..c41acc7841 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -5,8 +5,7 @@ module Rails module Generators module Actions - # Adds an entry into Gemfile for the supplied gem. If env - # is specified, add the gem to the given environment. + # Adds an entry into Gemfile for the supplied gem. # # gem "rspec", :group => :test # gem "technoweenie-restful-authentication", :lib => "restful-authentication", :source => "http://gems.github.com/" @@ -27,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 3d3b50540a..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 @@ -22,6 +22,7 @@ module Rails # # The only exception in ActiveModel for ActiveRecord is the use of self.build # instead of self.new. + # class ActiveModel attr_reader :name diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index f2ded6be84..28d7680669 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -161,6 +161,7 @@ module Rails # hook_for :resource_controller do |instance, controller| # instance.invoke controller, [ instance.name.pluralize ] # end + # def self.hook_for(*names, &block) options = names.extract_options! in_base = options.delete(:in) || base_name diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index e02f19508f..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 } @@ -168,6 +172,7 @@ module Rails # # If the generator is invoked with class name Admin, it will check for # the presence of "AdminObserver". + # def self.check_class_collision(options={}) define_method :check_class_collision do name = if self.respond_to?(:controller_class_name) # for ScaffoldBase 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..ab0e440bc4 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 @@ -232,6 +232,18 @@ task :default => :test public_task :apply_rails_template, :run_bundle + def name + @name ||= begin + # same as ActiveSupport::Inflector#underscore except not replacing '-' + underscored = original_name.dup + underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + underscored.downcase! + + underscored + end + end + protected def app_templates_dir @@ -268,18 +280,6 @@ task :default => :test @original_name ||= File.basename(destination_root) end - def name - @name ||= begin - # same as ActiveSupport::Inflector#underscore except not replacing '-' - underscored = original_name.dup - underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') - underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2') - underscored.downcase! - - underscored - end - end - def camelized @camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize 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/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index 3c12da359b..48833869e5 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -4,6 +4,7 @@ module Rails module Generators # Deal with controller names on scaffold and add some helpers to deal with # ActiveModel. + # module ResourceHelpers mattr_accessor :skip_warn @@ -12,6 +13,7 @@ module Rails end # Set controller variables on initialization. + # def initialize(*args) #:nodoc: super diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb index e8f5925ca5..ff9cf0087e 100644 --- a/railties/lib/rails/generators/test_case.rb +++ b/railties/lib/rails/generators/test_case.rb @@ -26,6 +26,7 @@ module Rails # destination File.expand_path("../tmp", File.dirname(__FILE__)) # setup :prepare_destination # end + # class TestCase < ActiveSupport::TestCase include FileUtils @@ -42,6 +43,7 @@ module Rails # Sets which generator should be tested: # # tests AppGenerator + # def self.tests(klass) self.generator_class = klass end @@ -50,6 +52,7 @@ module Rails # invoking it. # # arguments %w(app_name --skip-active-record) + # def self.arguments(array) self.default_arguments = array end @@ -57,6 +60,7 @@ module Rails # Sets the destination of generator files: # # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # def self.destination(path) self.destination_root = path end @@ -79,6 +83,7 @@ module Rails # assert_match(/Product\.all/, index) # end # end + # def assert_file(relative, *contents) absolute = File.expand_path(relative, destination_root) assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not" @@ -101,6 +106,7 @@ module Rails # path relative to the configured destination: # # assert_no_file "config/random.rb" + # def assert_no_file(relative) absolute = File.expand_path(relative, destination_root) assert !File.exists?(absolute), "Expected file #{relative.inspect} to not exist, but does" @@ -118,6 +124,7 @@ module Rails # assert_file "db/migrate/003_create_products.rb" # # Consequently, assert_migration accepts the same arguments has assert_file. + # def assert_migration(relative, *contents, &block) file_name = migration_file_name(relative) assert file_name, "Expected migration #{relative} to exist, but was not found" @@ -128,6 +135,7 @@ module Rails # path relative to the configured destination: # # assert_no_migration "db/migrate/create_products.rb" + # def assert_no_migration(relative) file_name = migration_file_name(relative) assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}" @@ -142,6 +150,7 @@ module Rails # assert_match(/create_table/, up) # end # end + # def assert_class_method(method, content, &block) assert_instance_method "self.#{method}", content, &block end @@ -154,6 +163,7 @@ module Rails # assert_match(/Product\.all/, index) # end # end + # def assert_instance_method(method, content) assert content =~ /def #{method}(\(.+\))?(.*?)\n end/m, "Expected to have method #{method}" yield $2.strip if block_given? @@ -164,6 +174,7 @@ module Rails # properly: # # assert_field_type :date, :date_select + # def assert_field_type(attribute_type, field_type) assert_equal(field_type, create_generated_attribute(attribute_type).field_type) end @@ -171,6 +182,7 @@ module Rails # Asserts the given attribute type gets a proper default value: # # assert_field_default_value :string, "MyString" + # def assert_field_default_value(attribute_type, value) assert_equal(value, create_generated_attribute(attribute_type).default) end @@ -204,6 +216,7 @@ module Rails # attribute type and, optionally, the attribute name: # # create_generated_attribute(:string, 'name') + # def create_generated_attribute(attribute_type, name = 'test', index = nil) Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':')) end diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 6b4bdb2921..5081074395 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -1,15 +1,33 @@ +require 'rails/application/route_inspector' + class Rails::InfoController < ActionController::Base + self.view_paths = File.join(File.dirname(__FILE__), 'templates') + layout 'application' + + before_filter :require_local! + + def index + redirect_to '/rails/info/routes' + end + def properties - if consider_all_requests_local? || request.local? - render :inline => Rails::Info.to_html - else - render :text => '<p>For security purposes, this information is only available to local requests.</p>', :status => :forbidden - end + @info = Rails::Info.to_html + end + + def routes + inspector = Rails::Application::RouteInspector.new + @info = inspector.format(_routes.routes).join("\n") end protected - def consider_all_requests_local? - Rails.application.config.consider_all_requests_local + def require_local! + unless local_request? + render :text => '<p>For security purposes, this information is only available to local requests.</p>', :status => :forbidden + end + end + + def local_request? + Rails.application.config.consider_all_requests_local || request.local? end end diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index b787d91821..6cd9c7bc95 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -1,5 +1,3 @@ -require "pathname" - module Rails module Paths # This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system. @@ -186,7 +184,7 @@ module Rails raise "You need to set a path root" unless @root.path map do |p| - Pathname.new(@root.path).join(p) + File.join @root.path, p end end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 8fbf58315a..2102f8a03c 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -110,6 +110,7 @@ module Rails # can be used in both. # # Be sure to look at the documentation of those specific classes for more information. + # class Railtie autoload :Configurable, "rails/railtie/configurable" autoload :Configuration, "rails/railtie/configuration" diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index eea8abe7d2..70370be3f5 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -60,7 +60,7 @@ namespace :db do end def find_engine_path(path) - return if path == "/" + return File.expand_path(Dir.pwd) if path == "/" if Rails::Engine.find(path) path diff --git a/railties/lib/rails/templates/layouts/application.html.erb b/railties/lib/rails/templates/layouts/application.html.erb new file mode 100644 index 0000000000..53276d3e7c --- /dev/null +++ b/railties/lib/rails/templates/layouts/application.html.erb @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8" /> + <title>Routes</title> + <style> + body { background-color: #fff; color: #333; } + + body, p, ol, ul, td { + font-family: helvetica, verdana, arial, sans-serif; + font-size: 13px; + line-height: 18px; + } + + pre { + background-color: #eee; + padding: 10px; + font-size: 11px; + white-space: pre-wrap; + } + + a { color: #000; } + a:visited { color: #666; } + a:hover { color: #fff; background-color:#000; } + </style> +</head> +<body> +<h2>Your App: <%= link_to 'properties', '/rails/info/properties' %> | <%= link_to 'routes', '/rails/info/routes' %></h2> +<%= yield %> + +</body> +</html> diff --git a/railties/lib/rails/templates/rails/info/properties.html.erb b/railties/lib/rails/templates/rails/info/properties.html.erb new file mode 100644 index 0000000000..d47cbab202 --- /dev/null +++ b/railties/lib/rails/templates/rails/info/properties.html.erb @@ -0,0 +1 @@ +<%= @info.html_safe %>
\ No newline at end of file diff --git a/railties/lib/rails/templates/rails/info/routes.html.erb b/railties/lib/rails/templates/rails/info/routes.html.erb new file mode 100644 index 0000000000..890f6f5b03 --- /dev/null +++ b/railties/lib/rails/templates/rails/info/routes.html.erb @@ -0,0 +1,9 @@ +<h2> + Routes +</h2> + +<p> + Routes match in priority from top to bottom +</p> + +<p><pre><%= @info %></pre></p>
\ No newline at end of file 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/application/routing_test.rb b/railties/test/application/routing_test.rb index 977a5fc7e8..d1373ba202 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -15,12 +15,24 @@ module ApplicationTests teardown_app end + test "rails/info/routes in development" do + app("development") + get "/rails/info/routes" + assert_equal 200, last_response.status + end + test "rails/info/properties in development" do app("development") get "/rails/info/properties" assert_equal 200, last_response.status end + test "rails/info/routes in production" do + app("production") + get "/rails/info/routes" + assert_equal 404, last_response.status + end + test "rails/info/properties in production" do app("production") get "/rails/info/properties" 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/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 6dc72f0fc0..e78e67725d 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -1,5 +1,6 @@ # # Tests, setup, and teardown common to the application and plugin generator suites. +# module SharedGeneratorTests def setup Rails.application = TestApp::Application diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index aa04cad033..5d6b6f9f72 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -29,7 +29,7 @@ class PathsTest < ActiveSupport::TestCase test "creating a root level path" do @root.add "app" assert_equal ["/foo/bar/app"], @root["app"].to_a - assert_equal [Pathname.new("/foo/bar/app")], @root["app"].paths + assert_equal ["/foo/bar/app"], @root["app"].paths end test "creating a root level path with options" do @@ -192,7 +192,7 @@ class PathsTest < ActiveSupport::TestCase @root["app"] = "/app" @root["app"].glob = "*.rb" assert_equal "*.rb", @root["app"].glob - assert_equal [Pathname.new("/app")], @root["app"].paths + assert_equal ["/foo/bar/app"], @root["app"].paths end test "it should be possible to override a path's default glob without assignment" do diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index f7a30a16d2..cfb32b7d35 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -12,29 +12,28 @@ class InfoControllerTest < ActionController::TestCase def setup Rails.application.routes.draw do get '/rails/info/properties' => "rails/info#properties" + get '/rails/info/routes' => "rails/info#routes" end - @request.stubs(:local? => true) - @controller.stubs(:consider_all_requests_local? => false) + @controller.stubs(:local_request? => true) @routes = Rails.application.routes Rails::InfoController.send(:include, @routes.url_helpers) end test "info controller does not allow remote requests" do - @request.stubs(:local? => false) + @controller.stubs(:local_request? => false) get :properties assert_response :forbidden end test "info controller renders an error message when request was forbidden" do - @request.stubs(:local? => false) + @controller.stubs(:local_request? => false) get :properties assert_select 'p' end test "info controller allows requests when all requests are considered local" do - @request.stubs(:local? => false) - @controller.stubs(:consider_all_requests_local? => true) + @controller.stubs(:local_request? => true) get :properties assert_response :success end @@ -48,4 +47,10 @@ class InfoControllerTest < ActionController::TestCase get :properties assert_select 'table' end + + test "info controller renders with routes" do + get :routes + assert_select 'pre' + end + end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 55f72f532f..4437e2c8af 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 @@ -1098,6 +1098,10 @@ YAML get("/assets/bar.js") assert_equal "// App's bar js\n;", last_response.body.strip + + # ensure that railties are not added twice + railties = Rails.application.ordered_railties.map(&:class) + assert_equal railties, railties.uniq end test "railties_order adds :all with lowest priority if not given" do |