diff options
151 files changed, 1845 insertions, 1329 deletions
@@ -2,27 +2,17 @@ source 'https://rubygems.org' gemspec -gem 'arel', github: 'rails/arel', branch: 'master' - gem 'mocha', '~> 0.13.0', require: false -gem 'rack-test', github: 'brynary/rack-test' gem 'rack-cache', '~> 1.2' gem 'bcrypt-ruby', '~> 3.0.0' -gem 'jquery-rails', '~> 2.2.0', github: 'rails/jquery-rails' +gem 'jquery-rails', '~> 2.2.0' gem 'turbolinks' -gem 'coffee-rails', github: 'rails/coffee-rails' - -gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders', branch: 'master' - -# Needed for compiling the ActionDispatch::Journey parser -gem 'racc', '>=1.4.6', require: false +gem 'coffee-rails', '~> 4.0.0.beta1' # This needs to be with require false to avoid # it being automatically loaded by sprockets gem 'uglifier', require: false -gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master' - group :doc do gem 'sdoc', github: 'voloko/sdoc' gem 'redcarpet', '~> 2.2.2', platforms: :ruby @@ -48,6 +38,9 @@ platforms :ruby do gem 'yajl-ruby' gem 'nokogiri', '>= 1.4.5' + # Needed for compiling the ActionDispatch::Journey parser + gem 'racc', '>=1.4.6', require: false + # AR gem 'sqlite3', '~> 1.3.6' diff --git a/RAILS_VERSION b/RAILS_VERSION index e1e048d8f0..14e1b44259 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -4.0.0.beta +4.0.0.beta1 diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 7e57fb39f3..8f74ac0928 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,5 +1,8 @@ ## Rails 4.0.0 (unreleased) ## + +## Rails 4.0.0.beta1 (February 25, 2013) ## + * Allow passing interpolations to `#default_i18n_subject`, e.g.: # config/locales/en.yml diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 0cb5dc6d64..997046b971 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -3,7 +3,7 @@ module ActionMailer MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 5f68fbc608..c45e14abf7 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,60 @@ ## Rails 4.0.0 (unreleased) ## +* `format: true` does not override existing format constraints. + Fixes #9466. + + Example: + + # This will force the .json extension. + get '/json_only', to: ok, format: true, constraints: { format: /json/ } + + *Yves Senn* + +* Skip valid encoding checks for non-String parameters that come + from the matched route's defaults. + Fixes #9435. + + Example: + + root to: 'main#posts', page: 1 + + *Yves Senn* + +* Don't verify Regexp requirements for non-Regexp `:constraints`. + Fixes #9432. + + Example: + + get '/photos.:format' => 'feeds#photos', constraints: {format: 'xml'} + + *Yves Senn* + +* Make `ActionDispatch::Journey::Path::Pattern#new` raise more meaningful exception message. + + *Thierry Zires* + +## Rails 4.0.0.beta1 (February 25, 2013) ## + +* Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser* + +* New applications use an encrypted session store by default. + + *Santiago Pastorino* + +* Determine the controller#action from only the matched path when using the + shorthand syntax. Previously the complete path was used, which led + to problems with nesting (scopes and namespaces). + Fixes #7554. + + Example: + + # This will route to questions#new. + scope ':locale' do + get 'questions/new' + end + + *Yves Senn* + * Remove support for parsing XML parameters from request. If you still want to parse XML parameters, please install `actionpack-xml_parser' gem. @@ -279,7 +334,7 @@ *Stephen Ausman + Fabrizio Regini + Angelo Capilleri* -* Add filter capability to ActionController logs for redirect locations: +* Add logging filter capability for redirect URLs: config.filter_redirect << 'http://please.hide.it/' @@ -378,23 +433,17 @@ Before: check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) - #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" /> + # => <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" /> After: check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) - #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> + # => <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> Fix #8108. *Daniel Fox, Grant Hutchins & Trace Wax* -* `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's - returned value if any. - Fix #8086. - - *Nikita Afanasenko* - * `date_select` helper accepts `with_css_classes: true` to add css classes similar with type of generated select tags. @@ -447,10 +496,6 @@ * Failsafe exception returns `text/plain`. *Steve Klabnik* -* Remove `rack-cache` dependency from Action Pack and declare it on Gemfile - - *Guillermo Iguaran* - * Rename internal variables on `ActionController::TemplateAssertions` to prevent naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore. Fix #7459. @@ -536,9 +581,7 @@ *Guillermo Iguaran* * `ActionDispatch::Session::MemCacheStore` now uses `dalli` instead of the deprecated - `memcache-client` gem. As side effect the autoloading of unloaded classes objects - saved as values in session isn't supported anymore when mem_cache session store is - used, this can have an impact in apps only when config.cache_classes is false. + `memcache-client` gem. *Arun Agrawal + Guillermo Iguaran* @@ -812,9 +855,9 @@ * `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* +* URL path parameters with invalid encoding now raise `ActionController::BadRequest`. *Andrew White* -* Malformed query and request parameter hashes 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 @@ -841,7 +884,7 @@ *Andrew White* -* `respond_to` and `respond_with` now raise ActionController::UnknownFormat instead +* `respond_to` and `respond_with` now raise `ActionController::UnknownFormat` instead of directly returning head 406. The exception is rescued and converted to 406 in the exception handling middleware. *Steven Soroka* @@ -871,7 +914,7 @@ * Remove the leading \n added by textarea on `assert_select`. *Santiago Pastorino* * Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms` - to `false`. This change breaks remote forms that need to work also without javascript, + to `false`. This change breaks remote forms that need to work also without JavaScript, so if you need such behavior, you can either set it to `true` or explicitly pass `authenticity_token: true` in form options. @@ -966,9 +1009,6 @@ * `check_box` with `:form` html5 attribute will now replicate the `:form` attribute to the hidden field as well. *Carlos Antonio da Silva* -* Turn off verbose mode of rack-cache, we still have X-Rack-Cache to - check that info. Closes #5245. *Santiago Pastorino* - * `label` form helper accepts `for: nil` to not generate the attribute. *Carlos Antonio da Silva* * Add `:format` option to `number_to_percentage`. *Rodrigo Flores* @@ -996,6 +1036,8 @@ not submitted with the form. This is a behavior change, previously the hidden tag had a value of the disabled checkbox. *Tadas Tamosauskas* +* `favicon_link_tag` helper will now use the favicon in app/assets by default. *Lucas Caton* + * `ActionView::Helpers::TextHelper#highlight` now defaults to the HTML5 `mark` element. *Brian Cardarella* diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 4b984d0558..f8e4cb4384 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -97,15 +97,23 @@ module AbstractController self.response_body = render_to_body(options) end - # Raw rendering of a template to a string. Just convert the results of - # render_response into a String. + # Raw rendering of a template to a string. + # + # It is similar to render, except that it does not + # set the response_body and it should be guaranteed + # to always return a string. + # + # If a component extends the semantics of response_body + # (as Action Controller extends it to be anything that + # responds to the method each), this method needs to + # overriden in order to still return a string. # :api: plugin def render_to_string(*args, &block) options = _normalize_render(*args, &block) render_to_body(options) end - # Raw rendering of a template to a Rack-compatible body. + # Raw rendering of a template. # :api: plugin def render_to_body(options = {}) _process_options(options) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 93568da9ef..834d44f045 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -420,7 +420,7 @@ module ActionController #:nodoc: end def response - @responses[format] || @responses[Mime::ALL] + @responses.fetch(format, @responses[Mime::ALL]) end def negotiate_format(request) diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 17379cf7ac..d275a854fd 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -50,6 +50,10 @@ module ActionController #:nodoc: config_accessor :request_forgery_protection_token self.request_forgery_protection_token ||= :authenticity_token + # Holds the class which implements the request forgery protection. + config_accessor :forgery_protection_strategy + self.forgery_protection_strategy = nil + # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode. config_accessor :allow_forgery_protection self.allow_forgery_protection = true if allow_forgery_protection.nil? @@ -82,14 +86,14 @@ module ActionController #:nodoc: # * <tt>:reset_session</tt> - Resets the session. # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified. def protect_from_forgery(options = {}) - include protection_method_module(options[:with] || :null_session) + self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token prepend_before_action :verify_authenticity_token, options end private - def protection_method_module(name) + def protection_method_class(name) ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify) rescue NameError raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session' @@ -97,17 +101,22 @@ module ActionController #:nodoc: end module ProtectionMethods - module NullSession - protected + class NullSession + def initialize(controller) + @controller = controller + end # This is the method that defines the application behavior when a request is found to be unverified. def handle_unverified_request + request = @controller.request request.session = NullSessionHash.new(request.env) request.env['action_dispatch.request.flash_hash'] = nil request.env['rack.session.options'] = { skip: true } request.env['action_dispatch.cookies'] = NullCookieJar.build(request) end + protected + class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: def initialize(env) super(nil, env) @@ -135,16 +144,20 @@ module ActionController #:nodoc: end end - module ResetSession - protected + class ResetSession + def initialize(controller) + @controller = controller + end def handle_unverified_request - reset_session + @controller.reset_session end end - module Exception - protected + class Exception + def initialize(controller) + @controller = controller + end def handle_unverified_request raise ActionController::InvalidAuthenticityToken @@ -153,6 +166,10 @@ module ActionController #:nodoc: end protected + def handle_unverified_request + forgery_protection_strategy.new(self).handle_unverified_request + end + # The actual before_action that is used. Modify this to change how you handle unverified requests. def verify_authenticity_token unless verified_request? diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index 4a571ec546..d37aa1fbe5 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -20,7 +20,7 @@ module ActionDispatch @separators = strexp.separators.join @anchored = strexp.anchor else - raise "wtf bro: #{strexp}" + raise ArgumentError, "Bad expression: #{strexp}" end @names = nil diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index ee29e5c59c..36a0db6e61 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -205,7 +205,7 @@ module ActionDispatch end # Removes the cookie on the client machine by setting the value to an empty string - # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in + # and the expiration date in the past. Like <tt>[]=</tt>, you can pass in # an options hash to delete cookies with extra data such as a <tt>:path</tt>. def delete(key, options = {}) return unless @cookies.has_key? key.to_s diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 6de9be63c5..93a2b52996 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -9,7 +9,7 @@ module ActionDispatch # at GetIp#calculate_ip. # # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] - # requires. Some Rack servers simply drop preceeding headers, and only report + # requires. Some Rack servers simply drop preceding headers, and only report # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers]. # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn) # then you should test your Rack server to make sure your data is good. diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0a41ed0fcf..4fbe156a72 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -50,7 +50,6 @@ module ActionDispatch class Mapping #:nodoc: IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format] ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} - SHORTHAND_REGEX = %r{/[\w/]+$} WILDCARD_PATH = %r{\*([^/\)]+)\)?$} attr_reader :scope, :path, :options, :requirements, :conditions, :defaults @@ -111,44 +110,18 @@ module ActionDispatch @options[:controller] ||= /.+?/ end - if using_match_shorthand?(path_without_format, @options) - to_shorthand = @options[:to].blank? - @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1') - end - - @options.merge!(default_controller_and_action(to_shorthand)) - end - - # match "account/overview" - def using_match_shorthand?(path, options) - path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX - end - - def normalize_format! - if options[:format] == true - options[:format] = /.+/ - elsif options[:format] == false - options.delete(:format) - end + @options.merge!(default_controller_and_action) end def normalize_requirements! constraints.each do |key, requirement| next unless segment_keys.include?(key) || key == :controller - - if requirement.source =~ ANCHOR_CHARACTERS_REGEX - raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" - end - - if requirement.multiline? - raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" - end - + verify_regexp_requirement(requirement) if requirement.is_a?(Regexp) @requirements[key] = requirement end if options[:format] == true - @requirements[:format] = /.+/ + @requirements[:format] ||= /.+/ elsif Regexp === options[:format] @requirements[:format] = options[:format] elsif String === options[:format] @@ -156,6 +129,16 @@ module ActionDispatch end end + def verify_regexp_requirement(requirement) + if requirement.source =~ ANCHOR_CHARACTERS_REGEX + raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" + end + + if requirement.multiline? + raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" + end + end + def normalize_defaults! @defaults.merge!(scope[:defaults]) if scope[:defaults] @defaults.merge!(options[:defaults]) if options[:defaults] @@ -214,7 +197,7 @@ module ActionDispatch Constraints.new(endpoint, blocks, @set.request_class) end - def default_controller_and_action(to_shorthand=nil) + def default_controller_and_action if to.respond_to?(:call) { } else @@ -227,7 +210,7 @@ module ActionDispatch controller ||= default_controller action ||= default_action - unless controller.is_a?(Regexp) || to_shorthand + unless controller.is_a?(Regexp) controller = [@scope[:module], controller].compact.join("/").presence end @@ -340,7 +323,6 @@ module ActionDispatch # because this means it will be matched first. As this is the most popular route # of most Rails applications, this is beneficial. def root(options = {}) - options = { :to => options } if options.is_a?(String) match '/', { :as => :root, :via => :get }.merge!(options) end @@ -437,11 +419,15 @@ module ActionDispatch # end # # [:constraints] - # Constrains parameters with a hash of regular expressions or an - # object that responds to <tt>matches?</tt> + # Constrains parameters with a hash of regular expressions + # or an object that responds to <tt>matches?</tt>. In addition, constraints + # other than path can also be specified with any object + # that responds to <tt>===</tt> (eg. String, Array, Range, etc.). # # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ } # + # match 'json_only', constraints: { format: 'json' } + # # class Blacklist # def matches?(request) request.remote_ip == '1.2.3.4' end # end @@ -1383,6 +1369,11 @@ module ActionDispatch paths = [path] + rest end + path_without_format = path.to_s.sub(/\(\.:format\)$/, '') + if using_match_shorthand?(path_without_format, options) + options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1') + end + options[:anchor] = true unless options.key?(:anchor) if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) @@ -1393,6 +1384,10 @@ module ActionDispatch self end + def using_match_shorthand?(path, options) + path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$} + end + def decomposed_match(path, options) # :nodoc: if on = options.delete(:on) send(on) { decomposed_match(path, options) } @@ -1429,7 +1424,15 @@ module ActionDispatch @set.add_route(app, conditions, requirements, defaults, as, anchor) end - def root(options={}) + def root(path, options={}) + if path.is_a?(String) + options[:to] = path + elsif path.is_a?(Hash) and options.empty? + options = path + else + raise ArgumentError, "must be called with a path and/or options" + end + if @scope[:scope_level] == :resources with_scope_level(:root) do scope(parent_resource.path) do diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index ff86f87d49..ca31b5e02e 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -31,6 +31,8 @@ module ActionDispatch # 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| + next unless value.respond_to?(:valid_encoding?) + unless value.valid_encoding? raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" end diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 94cbc8e478..5c87a9cd7c 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -3,7 +3,7 @@ module ActionPack MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/actionpack/lib/action_view/dependency_tracker.rb b/actionpack/lib/action_view/dependency_tracker.rb new file mode 100644 index 0000000000..a2a555dfcb --- /dev/null +++ b/actionpack/lib/action_view/dependency_tracker.rb @@ -0,0 +1,93 @@ +require 'thread_safe' + +module ActionView + class DependencyTracker + @trackers = ThreadSafe::Cache.new + + def self.find_dependencies(name, template) + tracker = @trackers[template.handler] + + if tracker.present? + tracker.call(name, template) + else + [] + end + end + + def self.register_tracker(extension, tracker) + handler = Template.handler_for_extension(extension) + @trackers[handler] = tracker + end + + def self.remove_tracker(handler) + @trackers.delete(handler) + end + + class ERBTracker + EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ + + # Matches: + # render partial: "comments/comment", collection: commentable.comments + # render "comments/comments" + # render 'comments/comments' + # render('comments/comments') + # + # render(@topic) => render("topics/topic") + # render(topics) => render("topics/topic") + # render(message.topics) => render("topics/topic") + RENDER_DEPENDENCY = / + render\s* # render, followed by optional whitespace + \(? # start an optional parenthesis for the render call + (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture + ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture + /x + + def self.call(name, template) + new(name, template).dependencies + end + + def initialize(name, template) + @name, @template = name, template + end + + def dependencies + render_dependencies + explicit_dependencies + end + + attr_reader :name, :template + private :name, :template + + private + + def source + template.source + end + + def directory + name.split("/")[0..-2].join("/") + end + + def render_dependencies + source.scan(RENDER_DEPENDENCY). + collect(&:second).uniq. + + # render(@topic) => render("topics/topic") + # render(topics) => render("topics/topic") + # render(message.topics) => render("topics/topic") + collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }. + + # render("headline") => render("message/headline") + collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }. + + # replace quotes from string renders + collect { |name| name.gsub(/["']/, "") } + end + + def explicit_dependencies + source.scan(EXPLICIT_DEPENDENCY).flatten.uniq + end + end + + register_tracker :erb, ERBTracker + end +end diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb index 4507861dcc..9324a1ac50 100644 --- a/actionpack/lib/action_view/digestor.rb +++ b/actionpack/lib/action_view/digestor.rb @@ -1,25 +1,8 @@ require 'thread_safe' +require 'action_view/dependency_tracker' module ActionView class Digestor - EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ - - # Matches: - # render partial: "comments/comment", collection: commentable.comments - # render "comments/comments" - # render 'comments/comments' - # render('comments/comments') - # - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") - RENDER_DEPENDENCY = / - render\s* # render, followed by optional whitespace - \(? # start an optional parenthesis for the render call - (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture - ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture - /x - cattr_reader(:cache) @@cache = ThreadSafe::Cache.new @@ -47,7 +30,7 @@ module ActionView end def dependencies - render_dependencies + explicit_dependencies + DependencyTracker.find_dependencies(name, template) rescue ActionView::MissingTemplate [] # File doesn't exist, so no dependencies end @@ -69,16 +52,16 @@ module ActionView name.gsub(%r|/_|, "/") end - def directory - name.split("/")[0..-2].join("/") - end - def partial? false end + def template + @template ||= finder.find(logical_name, [], partial?, formats: [ format ]) + end + def source - @source ||= finder.find(logical_name, [], partial?, formats: [ format ]).source + template.source end def dependency_digest @@ -89,26 +72,6 @@ module ActionView (template_digests + injected_dependencies).join("-") end - def render_dependencies - source.scan(RENDER_DEPENDENCY). - collect(&:second).uniq. - - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") - collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }. - - # render("headline") => render("message/headline") - collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }. - - # replace quotes from string renders - collect { |name| name.gsub(/["']/, "") } - end - - def explicit_dependencies - source.scan(EXPLICIT_DEPENDENCY).flatten.uniq - end - def injected_dependencies Array.wrap(options[:dependencies]) end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index bf78c00e4d..31e37893c6 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -152,7 +152,7 @@ module ActionView # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon' # # favicon_link_tag '/myicon.ico' - # # => <link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> + # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> # # Mobile Safari looks for a different <link> tag, pointing to an image that # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad. @@ -161,7 +161,7 @@ module ActionView # favicon_link_tag '/mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" /> # - def favicon_link_tag(source='/favicon.ico', options={}) + def favicon_link_tag(source='favicon.ico', options={}) tag('link', { :rel => 'shortcut icon', :type => 'image/vnd.microsoft.icon', diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index a989966613..d3953c26b7 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -642,6 +642,8 @@ module ActionView # <time datetime="2010-11-03">Yesterday</time> # time_tag Date.today, pubdate: true # => # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time> + # time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # => + # <time datetime="2010-W44">November 04, 2010</time> # # <%= time_tag Time.now do %> # <span>Right now</span> @@ -651,7 +653,7 @@ module ActionView options = args.extract_options! format = options.delete(:format) || :long content = args.first || I18n.l(date_or_time, :format => format) - datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339 + datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601 content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block) end diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index 34fc23ac1a..c29c1b1eea 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -32,7 +32,7 @@ module ActionView content_tag(:pre, object, :class => "debug_dump") rescue Exception # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback - content_tag(:code, object.to_yaml, :class => "debug_dump") + content_tag(:code, object.inspect, :class => "debug_dump") end end end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 86d9f94067..1adc8225f1 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -33,7 +33,7 @@ module ActionView # (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token # by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>. # This is helpful when you're fragment-caching the form. Remote forms get the - # authenticity from the <tt>meta</tt> tag, so embedding is unnecessary unless you + # authenticity token from the <tt>meta</tt> tag, so embedding is unnecessary unless you # support browsers without JavaScript. # * A list of parameters to feed to the URL the form will be posted to. # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb index 1df826fe2b..368bec1c70 100644 --- a/actionpack/test/activerecord/controller_runtime_test.rb +++ b/actionpack/test/activerecord/controller_runtime_test.rb @@ -8,6 +8,8 @@ ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime class ControllerRuntimeLogSubscriberTest < ActionController::TestCase class LogSubscriberController < ActionController::Base + respond_to :html + def show render :inline => "<%= Project.all %>" end @@ -16,6 +18,12 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase render :inline => "Zero DB runtime" end + def create + ActiveRecord::LogSubscriber.runtime += 100 + project = Project.last + respond_with(project, location: url_for(action: :show)) + end + def redirect Project.all redirect_to :action => 'show' @@ -64,6 +72,12 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase assert_match(/\(Views: [\d.]+ms \| ActiveRecord: 0.0ms\)/, @logger.logged(:info)[1]) end + def test_log_with_active_record_when_post + post :create + wait + assert_match(/ActiveRecord: ([1-9][\d.]+)ms\)/, @logger.logged(:info)[2]) + end + def test_log_with_active_record_when_redirecting get :redirect wait diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index ed013e2185..a9c62899b5 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -80,6 +80,13 @@ class RespondToController < ActionController::Base respond_to(:html, :xml) end + def using_defaults_with_all + respond_to do |type| + type.html + type.all{ render text: "ALL" } + end + end + def made_for_content_type respond_to do |type| type.rss { render :text => "RSS" } @@ -301,6 +308,20 @@ class RespondToControllerTest < ActionController::TestCase assert_equal "<p>Hello world!</p>\n", @response.body end + def test_using_defaults_with_all + @request.accept = "*/*" + get :using_defaults_with_all + assert_equal "HTML!", @response.body.strip + + @request.accept = "text/html" + get :using_defaults_with_all + assert_equal "HTML!", @response.body.strip + + @request.accept = "application/json" + get :using_defaults_with_all + assert_equal "ALL", @response.body + end + def test_using_defaults_with_type_list @request.accept = "*/*" get :using_defaults_with_type_list diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 5e821046db..93e94f0f48 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -456,6 +456,22 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal("/", routes.send(:root_path)) end + def test_named_route_root_with_hash + rs.draw do + root "hello#index", as: :index + end + + routes = setup_for_named_route + assert_equal("http://test.host/", routes.send(:index_url)) + assert_equal("/", routes.send(:index_path)) + end + + def test_root_without_path_raises_argument_error + assert_raises ArgumentError do + rs.draw { root nil } + end + end + def test_named_route_root_with_trailing_slash rs.draw do root "hello#index" diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 143733254b..89f406a3d0 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1146,6 +1146,33 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'api/products#list', @response.body end + def test_match_shorthand_inside_scope_with_variables_with_controller + draw do + scope ':locale' do + match 'questions/new', via: [:get] + end + end + + get '/de/questions/new' + assert_equal 'questions#new', @response.body + assert_equal 'de', @request.params[:locale] + end + + def test_match_shorthand_inside_nested_namespaces_and_scopes_with_controller + draw do + namespace :api do + namespace :v3 do + scope ':locale' do + get "products/list" + end + end + end + end + + get '/api/v3/en/products/list' + assert_equal 'api/v3/products#list', @response.body + end + def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper draw do resources :replies do @@ -1319,7 +1346,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'en', @request.params[:locale] end - def test_default_params + def test_default_string_params draw do get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home' get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' } @@ -1339,6 +1366,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'home', @request.params[:id] end + def test_default_integer_params + draw do + get 'inline_pages/(:page)', to: 'pages#show', page: 1 + get 'default_pages/(:page)', to: 'pages#show', defaults: { page: 1 } + + defaults page: 1 do + get 'scoped_pages/(:page)', to: 'pages#show' + end + end + + get '/inline_pages' + assert_equal 1, @request.params[:page] + + get '/default_pages' + assert_equal 1, @request.params[:page] + + get '/scoped_pages' + assert_equal 1, @request.params[:page] + end + def test_resource_constraints draw do resources :products, :constraints => { :id => /\d{4}/ } do @@ -3353,6 +3400,66 @@ class TestPortConstraints < ActionDispatch::IntegrationTest end end +class TestFormatConstraints < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + + get '/string', to: ok, constraints: { format: 'json' } + get '/regexp', to: ok, constraints: { format: /json/ } + get '/json_only', to: ok, format: true, constraints: { format: /json/ } + get '/xml_only', to: ok, format: 'xml' + end + end + + include Routes.url_helpers + def app; Routes end + + def test_string_format_constraints + get 'http://www.example.com/string' + assert_response :success + + get 'http://www.example.com/string.json' + assert_response :success + + get 'http://www.example.com/string.html' + assert_response :not_found + end + + def test_regexp_format_constraints + get 'http://www.example.com/regexp' + assert_response :success + + get 'http://www.example.com/regexp.json' + assert_response :success + + get 'http://www.example.com/regexp.html' + assert_response :not_found + end + + def test_enforce_with_format_true_with_constraint + get 'http://www.example.com/json_only.json' + assert_response :success + + get 'http://www.example.com/json_only.html' + assert_response :not_found + + get 'http://www.example.com/json_only' + assert_response :not_found + end + + def test_enforce_with_string + get 'http://www.example.com/xml_only.xml' + assert_response :success + + get 'http://www.example.com/xml_only' + assert_response :success + + get 'http://www.example.com/xml_only.json' + assert_response :not_found + end +end + class TestRouteDefaults < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb b/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb new file mode 100644 index 0000000000..9f1f855269 --- /dev/null +++ b/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb @@ -0,0 +1 @@ +HTML! diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb index 2b7227cd0d..ce02104181 100644 --- a/actionpack/test/journey/path/pattern_test.rb +++ b/actionpack/test/journey/path/pattern_test.rb @@ -75,6 +75,10 @@ module ActionDispatch end end + def test_to_raise_exception_with_bad_expression + assert_raise(ArgumentError, "Bad expression: []") { Pattern.new [] } + end + def test_to_regexp_with_extended_group strexp = Router::Strexp.new( '/page/:name', diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 185f742c7f..11614a45dc 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -200,7 +200,7 @@ class AssetTagHelperTest < ActionView::TestCase } FaviconLinkToTag = { - %(favicon_link_tag) => %(<link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />), + %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />), %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />), %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/vnd.microsoft.icon" />), %(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />), diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index f11adefad8..242b56a1fd 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1510,42 +1510,42 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on") end - + def test_date_select_with_selected @post = Post.new @post.written_on = Date.new(2004, 6, 15) - + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} expected << "</select>\n" - + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7" selected="selected">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} expected << "</select>\n" - + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} - + expected << "</select>\n" - + assert_dom_equal expected, date_select("post", "written_on", :selected => Date.new(2004, 07, 10)) end def test_date_select_with_selected_nil @post = Post.new @post.written_on = Date.new(2004, 6, 15) - + expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1"/>' + "\n" - + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} expected << %{<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} expected << "</select>\n" - + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} expected << %{<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} - + expected << "</select>\n" - + assert_dom_equal expected, date_select("post", "written_on", include_blank: true, discard_year: true, selected: nil) end @@ -2296,7 +2296,7 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true, selected: nil) end - + def test_datetime_select_defaults_to_time_zone_now_when_config_time_zone_is_set # The love zone is UTC+0 mytz = Class.new(ActiveSupport::TimeZone) { @@ -3160,14 +3160,14 @@ class DateHelperTest < ActionView::TestCase end def test_time_tag_with_date - date = Date.today - expected = "<time datetime=\"#{date.rfc3339}\">#{I18n.l(date, :format => :long)}</time>" + date = Date.new(2013, 2, 20) + expected = '<time datetime="2013-02-20">February 20, 2013</time>' assert_equal expected, time_tag(date) end def test_time_tag_with_time - time = Time.now - expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :long)}</time>" + time = Time.new(2013, 2, 20, 0, 0, 0, '+00:00') + expected = '<time datetime="2013-02-20T00:00:00+00:00">February 20, 2013 00:00</time>' assert_equal expected, time_tag(time) end @@ -3184,8 +3184,8 @@ class DateHelperTest < ActionView::TestCase end def test_time_tag_with_different_format - time = Time.now - expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :short)}</time>" + time = Time.new(2013, 2, 20, 0, 0, 0, '+00:00') + expected = '<time datetime="2013-02-20T00:00:00+00:00">20 Feb 00:00</time>' assert_equal expected, time_tag(time, :format => :short) end diff --git a/actionpack/test/template/dependency_tracker_test.rb b/actionpack/test/template/dependency_tracker_test.rb new file mode 100644 index 0000000000..9c68afbdbd --- /dev/null +++ b/actionpack/test/template/dependency_tracker_test.rb @@ -0,0 +1,46 @@ +require 'abstract_unit' +require 'action_view/dependency_tracker' + +class DependencyTrackerTest < ActionView::TestCase + Neckbeard = lambda {|template| template.source } + Bowtie = lambda {|template| template.source } + + class NeckbeardTracker + def self.call(name, template) + ["foo/#{name}"] + end + end + + class FakeTemplate + attr_reader :source, :handler + + def initialize(source, handler = Neckbeard) + @source, @handler = source, handler + end + end + + def tracker + ActionView::DependencyTracker + end + + def setup + ActionView::Template.register_template_handler :neckbeard, Neckbeard + tracker.register_tracker(:neckbeard, NeckbeardTracker) + end + + def teardown + tracker.remove_tracker(:neckbeard) + end + + def test_finds_tracker_by_template_handler + template = FakeTemplate.new("boo/hoo") + dependencies = tracker.find_dependencies("boo/hoo", template) + assert_equal ["foo/boo/hoo"], dependencies + end + + def test_returns_empty_array_if_no_tracker_is_found + template = FakeTemplate.new("boo/hoo", Bowtie) + dependencies = tracker.find_dependencies("boo/hoo", template) + assert_equal [], dependencies + end +end diff --git a/actionpack/test/template/digestor_test.rb b/actionpack/test/template/digestor_test.rb index 849e2981a6..e29cecabc0 100644 --- a/actionpack/test/template/digestor_test.rb +++ b/actionpack/test/template/digestor_test.rb @@ -2,10 +2,11 @@ require 'abstract_unit' require 'fileutils' class FixtureTemplate - attr_reader :source + attr_reader :source, :handler def initialize(template_path) @source = File.read(template_path) + @handler = ActionView::Template.handler_for_extension(:erb) rescue Errno::ENOENT raise ActionView::MissingTemplate.new([], "", [], true, []) end diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index d8fffe75ed..6e640889d2 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -1,323 +1,75 @@ -require 'abstract_unit' +require "abstract_unit" class NumberHelperTest < ActionView::TestCase tests ActionView::Helpers::NumberHelper - def kilobytes(number) - number * 1024 - end - - def megabytes(number) - kilobytes(number) * 1024 - end - - def gigabytes(number) - megabytes(number) * 1024 - end - - def terabytes(number) - gigabytes(number) * 1024 - end - def test_number_to_phone - assert_equal("555-1234", number_to_phone(5551234)) - assert_equal("800-555-1212", number_to_phone(8005551212)) - assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true})) - assert_equal("", number_to_phone("", {:area_code => true})) - assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) - assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123})) - assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " ")) - assert_equal("555.1212", number_to_phone(5551212, :delimiter => '.')) - assert_equal("800-555-1212", number_to_phone("8005551212")) - assert_equal("+1-800-555-1212", number_to_phone(8005551212, :country_code => 1)) - assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => '')) - assert_equal("22-555-1212", number_to_phone(225551212)) - assert_equal("+45-22-555-1212", number_to_phone(225551212, :country_code => 45)) - assert_equal '111<script></script>111<script></script>1111', number_to_phone(1111111111, :delimiter => "<script></script>") + assert_equal nil, number_to_phone(nil) + assert_equal "555-1234", number_to_phone(5551234) + assert_equal "(800) 555-1212 x 123", number_to_phone(8005551212, area_code: true, extension: 123) + assert_equal "+18005551212", number_to_phone(8005551212, country_code: 1, delimiter: "") end def test_number_to_currency - assert_equal("$1,234,567,890.50", number_to_currency(1234567890.50)) - assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506)) - assert_equal("-$1,234,567,890.50", number_to_currency(-1234567890.50)) - assert_equal("-$ 1,234,567,890.50", number_to_currency(-1234567890.50, {:format => "%u %n"})) - assert_equal("($1,234,567,890.50)", number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"})) - assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0})) - assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1})) - assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""})) - assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50")) - assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) - assert_equal("1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", {:unit => "Kč", :format => "%n %u", :negative_format => "%n - %u"})) - assert_equal '$1<script></script>01', number_to_currency(1.01, :separator => "<script></script>") - assert_equal '$1<script></script>000.00', number_to_currency(1000, :delimiter => "<script></script>") + assert_equal nil, number_to_currency(nil) + assert_equal "$1,234,567,890.50", number_to_currency(1234567890.50) + assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0) + assert_equal "1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", unit: "Kč", format: "%n %u", negative_format: "%n - %u") end def test_number_to_percentage - assert_equal("100.000%", number_to_percentage(100)) - assert_equal("100%", number_to_percentage(100, {:precision => 0})) - assert_equal("302.06%", number_to_percentage(302.0574, {:precision => 2})) - assert_equal("100.000%", number_to_percentage("100")) - assert_equal("1000.000%", number_to_percentage("1000")) - assert_equal("123.4%", number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true)) - assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ',')) - assert_equal("1000.000 %", number_to_percentage(1000, :format => "%n %")) - assert_equal '1<script></script>010%', number_to_percentage(1.01, :separator => "<script></script>") - assert_equal '1<script></script>000.000%', number_to_percentage(1000, :delimiter => "<script></script>") + assert_equal nil, number_to_percentage(nil) + assert_equal "100.000%", number_to_percentage(100) + assert_equal "100%", number_to_percentage(100, precision: 0) + assert_equal "123.4%", number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true) + assert_equal "1.000,000%", number_to_percentage(1000, delimiter: ".", separator: ",") end def test_number_with_delimiter - assert_equal("12,345,678", number_with_delimiter(12345678)) - assert_equal("0", number_with_delimiter(0)) - assert_equal("123", number_with_delimiter(123)) - assert_equal("123,456", number_with_delimiter(123456)) - assert_equal("123,456.78", number_with_delimiter(123456.78)) - assert_equal("123,456.789", number_with_delimiter(123456.789)) - assert_equal("123,456.78901", number_with_delimiter(123456.78901)) - assert_equal("123,456,789.78901", number_with_delimiter(123456789.78901)) - assert_equal("0.78901", number_with_delimiter(0.78901)) - assert_equal("123,456.78", number_with_delimiter("123456.78")) - end - - def test_number_with_delimiter_with_options_hash - assert_equal '12 345 678', number_with_delimiter(12345678, :delimiter => ' ') - assert_equal '12,345,678-05', number_with_delimiter(12345678.05, :separator => '-') - assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :separator => ',', :delimiter => '.') - assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',') - assert_equal '1<script></script>01', number_with_delimiter(1.01, :separator => "<script></script>") - assert_equal '1<script></script>000', number_with_delimiter(1000, :delimiter => "<script></script>") + assert_equal nil, number_with_delimiter(nil) + assert_equal "12,345,678", number_with_delimiter(12345678) + assert_equal "0", number_with_delimiter(0) end def test_number_with_precision - assert_equal("-111.235", number_with_precision(-111.2346)) - assert_equal("111.235", number_with_precision(111.2346)) - assert_equal("31.83", number_with_precision(31.825, :precision => 2)) - assert_equal("111.23", number_with_precision(111.2346, :precision => 2)) - assert_equal("111.00", number_with_precision(111, :precision => 2)) - assert_equal("111.235", number_with_precision("111.2346")) - assert_equal("31.83", number_with_precision("31.825", :precision => 2)) - assert_equal("3268", number_with_precision((32.6751 * 100.00), :precision => 0)) - assert_equal("112", number_with_precision(111.50, :precision => 0)) - assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0)) - assert_equal("0", number_with_precision(0, :precision => 0)) - assert_equal("0.00100", number_with_precision(0.001, :precision => 5)) - assert_equal("0.001", number_with_precision(0.00111, :precision => 3)) - assert_equal("10.00", number_with_precision(9.995, :precision => 2)) - assert_equal("11.00", number_with_precision(10.995, :precision => 2)) - assert_equal("0.00", number_with_precision(-0.001, :precision => 2)) - end - - def test_number_with_precision_with_custom_delimiter_and_separator - assert_equal '31,83', number_with_precision(31.825, :precision => 2, :separator => ',') - assert_equal '1.231,83', number_with_precision(1231.825, :precision => 2, :separator => ',', :delimiter => '.') - assert_equal '1<script></script>010', number_with_precision(1.01, :separator => "<script></script>") - assert_equal '1<script></script>000.000', number_with_precision(1000, :delimiter => "<script></script>") - end - - def test_number_with_precision_with_significant_digits - assert_equal "124000", number_with_precision(123987, :precision => 3, :significant => true) - assert_equal "120000000", number_with_precision(123987876, :precision => 2, :significant => true ) - assert_equal "40000", number_with_precision("43523", :precision => 1, :significant => true ) - assert_equal "9775", number_with_precision(9775, :precision => 4, :significant => true ) - assert_equal "5.4", number_with_precision(5.3923, :precision => 2, :significant => true ) - assert_equal "5", number_with_precision(5.3923, :precision => 1, :significant => true ) - assert_equal "1", number_with_precision(1.232, :precision => 1, :significant => true ) - assert_equal "7", number_with_precision(7, :precision => 1, :significant => true ) - assert_equal "1", number_with_precision(1, :precision => 1, :significant => true ) - assert_equal "53", number_with_precision(52.7923, :precision => 2, :significant => true ) - assert_equal "9775.00", number_with_precision(9775, :precision => 6, :significant => true ) - assert_equal "5.392900", number_with_precision(5.3929, :precision => 7, :significant => true ) - assert_equal "0.0", number_with_precision(0, :precision => 2, :significant => true ) - assert_equal "0", number_with_precision(0, :precision => 1, :significant => true ) - assert_equal "0.0001", number_with_precision(0.0001, :precision => 1, :significant => true ) - assert_equal "0.000100", number_with_precision(0.0001, :precision => 3, :significant => true ) - assert_equal "0.0001", number_with_precision(0.0001111, :precision => 1, :significant => true ) - assert_equal "10.0", number_with_precision(9.995, :precision => 3, :significant => true) - assert_equal "9.99", number_with_precision(9.994, :precision => 3, :significant => true) - assert_equal "11.0", number_with_precision(10.995, :precision => 3, :significant => true) - end - - def test_number_with_precision_with_strip_insignificant_zeros - assert_equal "9775.43", number_with_precision(9775.43, :precision => 4, :strip_insignificant_zeros => true ) - assert_equal "9775.2", number_with_precision(9775.2, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - assert_equal "0", number_with_precision(0, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - end - - def test_number_with_precision_with_significant_true_and_zero_precision - # Zero precision with significant is a mistake (would always return zero), - # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) - assert_equal "124", number_with_precision(123.987, :precision => 0, :significant => true) - assert_equal "12", number_with_precision(12, :precision => 0, :significant => true ) - assert_equal "12", number_with_precision("12.3", :precision => 0, :significant => true ) + assert_equal nil, number_with_precision(nil) + assert_equal "-111.235", number_with_precision(-111.2346) + assert_equal "111.00", number_with_precision(111, precision: 2) + assert_equal "0.00100", number_with_precision(0.001, precision: 5) end def test_number_to_human_size - assert_equal '0 Bytes', number_to_human_size(0) - assert_equal '1 Byte', number_to_human_size(1) - assert_equal '3 Bytes', number_to_human_size(3.14159265) - assert_equal '123 Bytes', number_to_human_size(123.0) - assert_equal '123 Bytes', number_to_human_size(123) - assert_equal '1.21 KB', number_to_human_size(1234) - assert_equal '12.1 KB', number_to_human_size(12345) - assert_equal '1.18 MB', number_to_human_size(1234567) - assert_equal '1.15 GB', number_to_human_size(1234567890) - assert_equal '1.12 TB', number_to_human_size(1234567890123) - assert_equal '1030 TB', number_to_human_size(terabytes(1026)) - assert_equal '444 KB', number_to_human_size(kilobytes(444)) - assert_equal '1020 MB', number_to_human_size(megabytes(1023)) - assert_equal '3 TB', number_to_human_size(terabytes(3)) - assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2) - assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) - assert_equal '123 Bytes', number_to_human_size('123') - assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 2) - assert_equal '1.01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4) - assert_equal '10 KB', number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 Byte', number_to_human_size(1.1) - assert_equal '10 Bytes', number_to_human_size(10) - end - - def test_number_to_human_size_with_si_prefix - assert_equal '3 Bytes', number_to_human_size(3.14159265, :prefix => :si) - assert_equal '123 Bytes', number_to_human_size(123.0, :prefix => :si) - assert_equal '123 Bytes', number_to_human_size(123, :prefix => :si) - assert_equal '1.23 KB', number_to_human_size(1234, :prefix => :si) - assert_equal '12.3 KB', number_to_human_size(12345, :prefix => :si) - assert_equal '1.23 MB', number_to_human_size(1234567, :prefix => :si) - assert_equal '1.23 GB', number_to_human_size(1234567890, :prefix => :si) - assert_equal '1.23 TB', number_to_human_size(1234567890123, :prefix => :si) - end - - def test_number_to_human_size_with_options_hash - assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2) - assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) - assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 2) - assert_equal '1.01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4) - assert_equal '10 KB', number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 TB', number_to_human_size(1234567890123, :precision => 1) - assert_equal '500 MB', number_to_human_size(524288000, :precision=>3) - assert_equal '10 MB', number_to_human_size(9961472, :precision=>0) - assert_equal '40 KB', number_to_human_size(41010, :precision => 1) - assert_equal '40 KB', number_to_human_size(41100, :precision => 2) - assert_equal '1.0 KB', number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false) - assert_equal '1.012 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false) - assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0 - assert_equal '9<script></script>86 KB', number_to_human_size(10100, :separator => "<script></script>") - end - - def test_number_to_human_size_with_custom_delimiter_and_separator - assert_equal '1,01 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :separator => ',') - assert_equal '1,01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4, :separator => ',') - assert_equal '1.000,1 TB', number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',') + assert_equal nil, number_to_human_size(nil) + assert_equal "3 Bytes", number_to_human_size(3.14159265) + assert_equal "1.2 MB", number_to_human_size(1234567, precision: 2) end def test_number_to_human - assert_equal '-123', number_to_human(-123) - assert_equal '-0.5', number_to_human(-0.5) - assert_equal '0', number_to_human(0) - assert_equal '0.5', number_to_human(0.5) - assert_equal '123', number_to_human(123) - assert_equal '1.23 Thousand', number_to_human(1234) - assert_equal '12.3 Thousand', number_to_human(12345) - assert_equal '1.23 Million', number_to_human(1234567) - assert_equal '1.23 Billion', number_to_human(1234567890) - assert_equal '1.23 Trillion', number_to_human(1234567890123) - assert_equal '1.23 Quadrillion', number_to_human(1234567890123456) - assert_equal '1230 Quadrillion', number_to_human(1234567890123456789) - assert_equal '490 Thousand', number_to_human(489939, :precision => 2) - assert_equal '489.9 Thousand', number_to_human(489939, :precision => 4) - assert_equal '489 Thousand', number_to_human(489000, :precision => 4) - assert_equal '489.0 Thousand', number_to_human(489000, :precision => 4, :strip_insignificant_zeros => false) - assert_equal '1.2346 Million', number_to_human(1234567, :precision => 4, :significant => false) - assert_equal '1,2 Million', number_to_human(1234567, :precision => 1, :significant => false, :separator => ',') - assert_equal '1 Million', number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false - end - - def test_number_to_human_with_custom_units - #Only integers - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123 lt', number_to_human(123456, :units => volume) - assert_equal '12 ml', number_to_human(12, :units => volume) - assert_equal '1.23 m3', number_to_human(1234567, :units => volume) - - #Including fractionals - distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - assert_equal '1.23 mm', number_to_human(0.00123, :units => distance) - assert_equal '1.23 cm', number_to_human(0.0123, :units => distance) - assert_equal '1.23 dm', number_to_human(0.123, :units => distance) - assert_equal '1.23 m', number_to_human(1.23, :units => distance) - assert_equal '1.23 dam', number_to_human(12.3, :units => distance) - assert_equal '1.23 hm', number_to_human(123, :units => distance) - assert_equal '1.23 km', number_to_human(1230, :units => distance) - assert_equal '1.23 km', number_to_human(1230, :units => distance) - assert_equal '1.23 km', number_to_human(1230, :units => distance) - assert_equal '12.3 km', number_to_human(12300, :units => distance) - - #The quantifiers don't need to be a continuous sequence - gangster = {:hundred => "hundred bucks", :million => "thousand quids"} - assert_equal '1 hundred bucks', number_to_human(100, :units => gangster) - assert_equal '25 hundred bucks', number_to_human(2500, :units => gangster) - assert_equal '25 thousand quids', number_to_human(25000000, :units => gangster) - assert_equal '12300 thousand quids', number_to_human(12345000000, :units => gangster) - - #Spaces are stripped from the resulting string - assert_equal '4', number_to_human(4, :units => {:unit => "", :ten => 'tens '}) - assert_equal '4.5 tens', number_to_human(45, :units => {:unit => "", :ten => ' tens '}) - - assert_equal '1<script></script>01', number_to_human(1.01, :separator => "<script></script>") - assert_equal '100<script></script>000 Quadrillion', number_to_human(10**20, :delimiter => "<script></script>") + assert_equal nil, number_to_human(nil) + assert_equal "0", number_to_human(0) + assert_equal "1.23 Thousand", number_to_human(1234) + assert_equal "489.0 Thousand", number_to_human(489000, precision: 4, strip_insignificant_zeros: false) end - def test_number_to_human_with_custom_format - assert_equal '123 times Thousand', number_to_human(123456, :format => "%n times %u") - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123.lt', number_to_human(123456, :units => volume, :format => "%n.%u") - end - - def test_number_helpers_should_return_nil_when_given_nil - assert_nil number_to_phone(nil) - assert_nil number_to_currency(nil) - assert_nil number_to_percentage(nil) - assert_nil number_with_delimiter(nil) - assert_nil number_with_precision(nil) - assert_nil number_to_human_size(nil) - assert_nil number_to_human(nil) - end + def test_number_helpers_escape_delimiter_and_separator + assert_equal "111<script></script>111<script></script>1111", number_to_phone(1111111111, delimiter: "<script></script>") - def test_number_helpers_do_not_mutate_options_hash - options = { 'raise' => true } + assert_equal "$1<script></script>01", number_to_currency(1.01, separator: "<script></script>") + assert_equal "$1<script></script>000.00", number_to_currency(1000, delimiter: "<script></script>") - number_to_phone(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal "1<script></script>010%", number_to_percentage(1.01, separator: "<script></script>") + assert_equal "1<script></script>000.000%", number_to_percentage(1000, delimiter: "<script></script>") - number_to_currency(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal "1<script></script>01", number_with_delimiter(1.01, separator: "<script></script>") + assert_equal "1<script></script>000", number_with_delimiter(1000, delimiter: "<script></script>") - number_to_percentage(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal "1<script></script>010", number_with_precision(1.01, separator: "<script></script>") + assert_equal "1<script></script>000.000", number_with_precision(1000, delimiter: "<script></script>") - number_with_delimiter(1, options) - assert_equal({ 'raise' => true }, options) - - number_with_precision(1, options) - assert_equal({ 'raise' => true }, options) - - number_to_human_size(1, options) - assert_equal({ 'raise' => true }, options) - - number_to_human(1, options) - assert_equal({ 'raise' => true }, options) - end + assert_equal "9<script></script>86 KB", number_to_human_size(10100, separator: "<script></script>") - def test_number_helpers_should_return_non_numeric_param_unchanged - assert_equal("+1-x x 123", number_to_phone("x", :country_code => 1, :extension => 123)) - assert_equal("x", number_to_phone("x")) - assert_equal("$x.", number_to_currency("x.")) - assert_equal("$x", number_to_currency("x")) - assert_equal("x%", number_to_percentage("x")) - assert_equal("x", number_with_delimiter("x")) - assert_equal("x.", number_with_precision("x.")) - assert_equal("x", number_with_precision("x")) - assert_equal "x", number_to_human_size('x') - assert_equal "x", number_to_human('x') + assert_equal "1<script></script>01", number_to_human(1.01, separator: "<script></script>") + assert_equal "100<script></script>000 Quadrillion", number_to_human(10**20, delimiter: "<script></script>") end def test_number_helpers_outputs_are_html_safe @@ -332,8 +84,8 @@ class NumberHelperTest < ActionView::TestCase assert number_to_human_size("asdf".html_safe).html_safe? assert number_to_human_size("1".html_safe).html_safe? - assert number_with_precision(1, :strip_insignificant_zeros => false).html_safe? - assert number_with_precision(1, :strip_insignificant_zeros => true).html_safe? + assert number_with_precision(1, strip_insignificant_zeros: false).html_safe? + assert number_with_precision(1, strip_insignificant_zeros: true).html_safe? assert !number_with_precision("<script></script>").html_safe? assert number_with_precision("asdf".html_safe).html_safe? assert number_with_precision("1".html_safe).html_safe? @@ -362,37 +114,37 @@ class NumberHelperTest < ActionView::TestCase def test_number_helpers_should_raise_error_if_invalid_when_specified exception = assert_raise InvalidNumberError do - number_to_human("x", :raise => true) + number_to_human("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_to_human_size("x", :raise => true) + number_to_human_size("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_with_precision("x", :raise => true) + number_with_precision("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_to_currency("x", :raise => true) + number_to_currency("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_to_percentage("x", :raise => true) + number_to_percentage("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_with_delimiter("x", :raise => true) + number_with_delimiter("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_to_phone("x", :raise => true) + number_to_phone("x", raise: true) end assert_equal "x", exception.number end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 09e6ede064..1fe6dbd4d9 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,5 +1,8 @@ ## Rails 4.0.0 (unreleased) ## + +## Rails 4.0.0.beta1 (February 25, 2013) ## + * Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the absence of attributes. @@ -19,7 +22,7 @@ *Roberto Vasquez Angel* -* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!` +* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`. *Renato Mascarenhas* @@ -27,9 +30,11 @@ *Rafael Mendonça França* -* Specify type of singular association during serialization *Steve Klabnik* +* Specify type of singular association during serialization. + + *Steve Klabnik* -* Fixed length validator to correctly handle nil values. Fixes #7180. +* Fixed length validator to correctly handle `nil`. Fixes #7180. *Michal Zima* @@ -53,24 +58,27 @@ *Guillermo Iguaran* -* Due to a change in builder, nil values and empty strings now generates +* Due to a change in builder, `nil` and empty strings now generate closed tags, so instead of this: <pseudonyms nil=\"true\"></pseudonyms> - It generates this: + it generates this: <pseudonyms nil=\"true\"/> *Carlos Antonio da Silva* -* Changed inclusion and exclusion validators to accept a symbol for `:in` option. +* Inclusion/exclusion validators accept a method name passed as a symbol to the + `:in` option. - This allows to use dynamic inclusion/exclusion values using methods, besides the current lambda/proc support. + This allows to use dynamic inclusion/exclusion values using methods, besides + the current lambda/proc support. *Gabriel Sobrinho* -* `AM::Validation#validates` ability to pass custom exception to `:strict` option. +* `ActiveModel::Validation#validates` ability to pass custom exception to the + `:strict` option. *Bogdan Gusiev* @@ -81,7 +89,7 @@ *Anthony Alberto* -* Changed `AM::Serializers::JSON.include_root_in_json' default value to false. +* Changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to false. Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578. class User < ActiveRecord::Base; end @@ -108,16 +116,31 @@ *Francesco Rodriguez* -* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell* +* Passing false hash values to `validates` will no longer enable the corresponding validators. + + *Steve Purcell* + +* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute`. + + *Brian Cardarella* -* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella* +* Added `ActiveModel::Model`, a mixin to make Ruby objects work with AP out of box. + + *Guillermo Iguaran* + +* `AM::Errors#to_json`: support `:full_messages` parameter. + + *Bogdan Gusiev* -* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran* +* Trim down Active Model API by removing `valid?` and `errors.full_messages`. -* `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev* + *José Valim* -* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim* +* When `^` or `$` are used in the regular expression provided to `validates_format_of` + and the `:multiline` option is not set to true, an exception will be raised. This is + to prevent security vulnerabilities when using `validates_format_of`. The problem is + described in detail in the Rails security guide. -* When `^` or `$` are used in the regular expression provided to `validates_format_of` and the :multiline option is not set to true, an exception will be raised. This is to prevent security vulnerabilities when using `validates_format_of`. The problem is described in detail in the Rails security guide *Jan Berdajs + Egor Homakov* + *Jan Berdajs + Egor Homakov* Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index e195c12a4d..7586b02037 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -3,7 +3,7 @@ module ActiveModel MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3410f38063..a5d74418c4 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,92 @@ ## Rails 4.0.0 (unreleased) ## +* Support PostgreSQL specific column types when using `change_table`. + Fixes #9480. + + Example: + + change_table :authors do |t| + t.hstore :books + t.json :metadata + end + + *Yves Senn* + +* Revert 408227d9c5ed7d, 'quote numeric'. This introduced some regressions. + + *Steve Klabnik* + +* Fix calculation of `db_runtime` property in + `ActiveRecord::Railties::ControllerRuntime#cleanup_view_runtime`. + Previously, after raising `ActionView::MissingTemplate`, `db_runtime` was + not populated. + Fixes #9215. + + *Igor Fedoronchuk* + +* Do not try to touch invalid (and thus not persisted) parent record + for a `belongs_to :parent, touch: true` association + + *Olek Janiszewski* + +* Fix when performing an ordered join query. The bug only + affected queries where the order was given with a symbol. + Fixes #9275. + + Example: + + # This will expand the order :name to "authors".name. + Author.joins(:books).where('books.published = 1').order(:name) + + +## Rails 4.0.0.beta1 (February 25, 2013) ## + +* Fix overriding of attributes by `default_scope` on `ActiveRecord::Base#dup`. + + *Hiroshige UMINO* + +* Update queries now use prepared statements. + + *Olli Rissanen* + +* Fixing issue #8345. Now throwing an error when one attempts to touch a + new object that has not yet been persisted. For instance: + + Example: + + ball = Ball.new + ball.touch :updated_at # => raises error + + It is not until the ball object has been persisted that it can be touched. + This follows the behavior of update_column. + + *John Wang* + +* Preloading ordered `has_many :through` associations no longer applies + invalid ordering to the `:through` association. + Fixes #8663. + + *Yves Senn* + +* The auto explain feature has been removed. This feature was + activated by configuring `config.active_record.auto_explain_threshold_in_seconds`. + The configuration option was deprecated and has no more effect. + + You can still use `ActiveRecord::Relation#explain` to see the EXPLAIN output for + any given relation. + + *Yves Senn* + +* The `:on` option for `after_commit` and `after_rollback` now + accepts an Array of actions. + Fixes #988. + + Example: + + after_commit :update_cache on: [:create, :update] + + *Yves Senn* + * Rename related indexes on `rename_table` and `rename_column`. This does not affect indexes with custom names. @@ -143,8 +230,8 @@ *Justin George* -* The `DATABASE_URL` environment variable now converts ints, floats, and - the strings true and false to Ruby types. For example, SQLite requires +* The database adpters now converts the options passed thought `DATABASE_URL` + environment variable to the proper Ruby types before using. For example, SQLite requires that the timeout value is an integer, and PostgreSQL requires that the prepared_statements option is a boolean. These now work as expected: @@ -153,7 +240,7 @@ DATABASE_URL=sqlite3://localhost/test_db?timeout=500 DATABASE_URL=postgresql://localhost/test_db?prepared_statements=false - *Aaron Stone* + *Aaron Stone + Rafael Mendonça França* * `Relation#merge` now only overwrites where values on the LHS of the merge. Consider: @@ -1214,12 +1301,11 @@ * `:conditions` becomes `:where`. * `:include` becomes `:includes`. - The code to implement the deprecated features has been moved out to - the `activerecord-deprecated_finders` gem. This gem is a dependency - of Active Record in Rails 4.0. It will no longer be a dependency - from Rails 4.1, but if your app relies on the deprecated features - then you can add it to your own Gemfile. It will be maintained by - the Rails core team until Rails 5.0 is released. + The code to implement the deprecated features has been moved out to the + `activerecord-deprecated_finders` gem. This gem is a dependency of Active + Record in Rails 4.0, so the interface works out of the box. It will no + longer be a dependency from Rails 4.1 (you'll need to add it to the + `Gemfile` in 4.1), and will be maintained until Rails 5.0. *Jon Leighton* diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc index bdd8834dcb..2f3d516c43 100644 --- a/activerecord/RUNNING_UNIT_TESTS.rdoc +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc @@ -2,7 +2,7 @@ If you don't have the environment set make sure to read - http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#testing-active-record. + http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#testing-active-record == Running the Tests diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index d523c1eca1..89a62f0873 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -24,6 +24,6 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'activemodel', version - s.add_dependency 'arel', '~> 3.0.2' + s.add_dependency 'arel', '~> 4.0.0.beta1' s.add_dependency 'activerecord-deprecated_finders', '~> 0.0.3' end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 06bdabfced..513d1012ba 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -965,8 +965,8 @@ module ActiveRecord # For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they # cause the records in the join table to be removed. # - # For +has_many+, <tt>destroy</tt> will always call the <tt>destroy</tt> method of the - # record(s) being removed so that callbacks are run. However <tt>delete</tt> will either + # For +has_many+, <tt>destroy</tt> and <tt>destory_all</tt> will always call the <tt>destroy</tt> method of the + # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or # if no <tt>:dependent</tt> option is given, then it will follow the default strategy. # The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 3f0e4ca999..868095f068 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -146,7 +146,7 @@ module ActiveRecord def interpolate(sql, record = nil) if sql.respond_to?(:to_proc) - owner.send(:instance_exec, record, &sql) + owner.instance_exec(record, &sql) else sql end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 532af3e83e..54b1a69774 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -1,5 +1,5 @@ module ActiveRecord - # = Active Record Belongs To Associations + # = Active Record Belongs To Association module Associations class BelongsToAssociation < SingularAssociation #:nodoc: diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 2f2600b7fb..97b1ff18e2 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -48,7 +48,7 @@ module ActiveRecord::Associations::Builder def belongs_to_touch_after_save_or_destroy_for_#{name} record = #{name} - unless record.nil? + unless record.nil? || record.new_record? record.touch #{options[:touch].inspect if options[:touch] != true} end end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index 711f7b3ce1..5604687b57 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -70,7 +70,7 @@ module ActiveRecord end def instantiate(row) - @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row)) + @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row)) end end end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index b0b1c13b0d..c4b50ab306 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -47,12 +47,12 @@ module ActiveRecord through_scope.where! reflection.foreign_type => options[:source_type] else unless reflection_scope.where_values.empty? - through_scope.includes_values = reflection_scope.values[:includes] || options[:source] + through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) through_scope.where_values = reflection_scope.values[:where] end - through_scope.order! reflection_scope.values[:order] through_scope.references! reflection_scope.values[:references] + through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading? end through_scope diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 704998301c..55542262b0 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -347,7 +347,7 @@ module ActiveRecord end # reconstruct the scope now that we know the owner's id - association.send(:reset_scope) if association.respond_to?(:reset_scope) + association.reset_scope if association.respond_to?(:reset_scope) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index aec4654eee..d18b9c991f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -25,19 +25,13 @@ module ActiveRecord when true, false if column && column.type == :integer value ? '1' : '0' - elsif column && [:text, :string, :binary].include?(column.type) - value ? "'1'" : "'0'" else value ? quoted_true : quoted_false end # BigDecimals need to be put in a non-normalized form and quoted. when nil then "NULL" - when Numeric, ActiveSupport::Duration - value = BigDecimal === value ? value.to_s('F') : value.to_s - if column && ![:integer, :float, :decimal].include?(column.type) - value = "'#{value}'" - end - value + when BigDecimal then value.to_s('F') + when Numeric, ActiveSupport::Duration then value.to_s when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value.to_s}'" 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 f758e19a4f..42206de8fc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -44,8 +44,8 @@ module ActiveRecord # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # - # Inside migration files, the +t+ object in +create_table+ and - # +change_table+ is actually of this type: + # Inside migration files, the +t+ object in +create_table+ + # is actually of this type: # # class SomeMigration < ActiveRecord::Migration # def up @@ -489,7 +489,7 @@ module ActiveRecord args.each do |name| @base.add_column(@table_name, name, column_type, options) end - end + end end private diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index fd5eaab9c9..f587bf8140 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -1,10 +1,12 @@ +require 'ipaddr' + module ActiveRecord module ConnectionAdapters # :nodoc: # The goal of this module is to move Adapter specific column # definitions to the Adapter instead of having it in the schema # dumper itself. This code represents the normal case. # We can then redefine how certain data types may be handled in the schema dumper on the - # Adapter level by over-writing this code inside the database spececific adapters + # Adapter level by over-writing this code inside the database specific adapters module ColumnDumper def column_spec(column, types) spec = prepare_column_options(column, types) @@ -50,6 +52,15 @@ module ActiveRecord when Range # infinity dumps as Infinity, which causes uninitialized constant error value.inspect.gsub('Infinity', '::Float::INFINITY') + when IPAddr + subnet_mask = value.instance_variable_get(:@mask_addr) + + # If the subnet mask is equal to /32, don't output it + if subnet_mask == (2**32 - 1) + "\"#{value.to_s}\"" + else + "\"#{value.to_s}/#{subnet_mask.to_s(2).count('1')}\"" + end else value.inspect end 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 9bae880024..0cce8c7596 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -156,7 +156,7 @@ module ActiveRecord # # See also TableDefinition#column for details on how to create columns. def create_table(table_name, options = {}) - td = table_definition + td = create_table_definition td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false yield td if block_given? @@ -298,10 +298,10 @@ module ActiveRecord def change_table(table_name, options = {}) if supports_bulk_alter? && options[:bulk] recorder = ActiveRecord::Migration::CommandRecorder.new(self) - yield Table.new(table_name, recorder) + yield update_table_definition(table_name, recorder) bulk_change_table(table_name, recorder.commands) else - yield Table.new(table_name, self) + yield update_table_definition(table_name, self) end end @@ -727,9 +727,13 @@ module ActiveRecord end private - def table_definition + def create_table_definition TableDefinition.new(self) end + + def update_table_definition(table_name, base) + Table.new(table_name, base) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 67d4d505f7..ff9de712bc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -61,12 +61,30 @@ module ActiveRecord include MonitorMixin include ColumnDumper + SIMPLE_INT = /\A\d+\z/ + define_callbacks :checkout, :checkin attr_accessor :visitor, :pool attr_reader :schema_cache, :last_use, :in_use, :logger alias :in_use? :in_use + def self.type_cast_config_to_integer(config) + if config =~ SIMPLE_INT + config.to_i + else + config + end + end + + def self.type_cast_config_to_boolean(config) + if config == "false" + false + else + config + end + end + def initialize(connection, logger = nil, pool = nil) #:nodoc: super() diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 0871454cfe..9826b18053 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -31,7 +31,7 @@ module ActiveRecord return false if blob_or_text_column? #mysql forbids defaults on blob and text columns super end - + def blob_or_text_column? sql_type =~ /blob/i || type == :text end @@ -140,7 +140,7 @@ module ActiveRecord @connection_options, @config = connection_options, config @quoted_column_names, @quoted_table_names = {}, {} - if config.fetch(:prepared_statements) { true } + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::MySQL.new self else @visitor = BindSubstitution.new self @@ -212,6 +212,8 @@ module ActiveRecord if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) s = column.class.string_to_binary(value).unpack("H*")[0] "x'#{s}'" + elsif value.kind_of?(BigDecimal) + value.to_s("F") else super end @@ -581,7 +583,7 @@ module ActiveRecord end def strict_mode? - @config.fetch(:strict, true) + self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end protected @@ -722,7 +724,7 @@ module ActiveRecord # Increase timeout so the server doesn't disconnect us. wait_timeout = @config[:wait_timeout] wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) - variables[:wait_timeout] = wait_timeout + variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout) # Make MySQL reject illegal values rather than truncating or blanking them, see # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index de418eab14..2c683fc3ac 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -65,10 +65,6 @@ module ActiveRecord ConnectionSpecification.new(spec, adapter_method) end - # For DATABASE_URL, accept a limited concept of ints and floats - SIMPLE_INT = /\A\d+\z/ - SIMPLE_FLOAT = /\A\d+\.\d+\z/ - def self.connection_url_to_hash(url) # :nodoc: config = URI.parse url adapter = config.scheme @@ -89,28 +85,11 @@ module ActiveRecord if config.query options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys - options.each { |key, value| options[key] = type_cast_value(value) } - spec.merge!(options) end spec end - - def self.type_cast_value(value) - case value - when SIMPLE_INT - value.to_i - when SIMPLE_FLOAT - value.to_f - when 'true' - true - when 'false' - false - else - value - end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 631f646f58..7544c2a783 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -126,7 +126,7 @@ module ActiveRecord def initialize(connection, logger, connection_options, config) super @statements = StatementPool.new(@connection, - config.fetch(:statement_limit) { 1000 }) + self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @client_encoding = nil connect end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 0818760b11..b39eb058ae 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -263,7 +263,7 @@ module ActiveRecord attr_accessor :array end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + module ColumnMethods def xml(*args) options = args.extract_options! column(args[0], 'xml', options) @@ -325,6 +325,10 @@ module ActiveRecord def json(name, options = {}) column(name, 'json', options) end + end + + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods def column(name, type = nil, options = {}) super @@ -344,6 +348,10 @@ module ActiveRecord end end + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + ADAPTER_NAME = 'PostgreSQL' NATIVE_DATABASE_TYPES = { @@ -478,7 +486,7 @@ module ActiveRecord def initialize(connection, logger, connection_parameters, config) super(connection, logger) - if config.fetch(:prepared_statements) { true } + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::PostgreSQL.new self else @visitor = BindSubstitution.new self @@ -492,7 +500,7 @@ module ActiveRecord connect @statements = StatementPool.new @connection, - config.fetch(:statement_limit) { 1000 } + self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) if postgresql_version < 80200 raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" @@ -500,7 +508,7 @@ module ActiveRecord initialize_type_map @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] - @use_insert_returning = @config.key?(:insert_returning) ? @config[:insert_returning] : true + @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true end # Clears the prepared statements cache. @@ -884,9 +892,13 @@ module ActiveRecord $1.strip if $1 end - def table_definition + def create_table_definition TableDefinition.new(self) end + + def update_table_definition(table_name, base) + Table.new(table_name, base) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 6ede5f3390..981c4c96a0 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -26,7 +26,7 @@ module ActiveRecord :results_as_hash => true ) - db.busy_timeout(config[:timeout]) if config[:timeout] + db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] ConnectionAdapters::SQLite3Adapter.new(db, logger, config) end @@ -107,10 +107,10 @@ module ActiveRecord @active = nil @statements = StatementPool.new(@connection, - config.fetch(:statement_limit) { 1000 }) + self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @config = config - if config.fetch(:prepared_statements) { true } + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::SQLite.new self else @visitor = BindSubstitution.new self diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 6510433056..899fe7d7c7 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -249,7 +249,6 @@ module ActiveRecord @new_record = true ensure_proper_type - populate_with_current_scope_attributes super end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index 70683eb731..b2a9a54af1 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -2,43 +2,7 @@ require 'active_support/lazy_load_hooks' module ActiveRecord module Explain - def self.extended(base) - base.mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false - end - - # If the database adapter supports explain and auto explain is enabled, - # this method triggers EXPLAIN logging for the queries triggered by the - # block if it takes more than the threshold as a whole. That is, the - # threshold is not checked against each individual query, but against the - # duration of the entire block. This approach is convenient for relations. - - # - # The available_queries_for_explain thread variable collects the queries - # to be explained. If the value is nil, it means queries are not being - # currently collected. A false value indicates collecting is turned - # off. Otherwise it is an array of queries. - def logging_query_plan # :nodoc: - return yield unless logger - - threshold = auto_explain_threshold_in_seconds - current = Thread.current - if connection.supports_explain? && threshold && current[:available_queries_for_explain].nil? - begin - queries = current[:available_queries_for_explain] = [] - start = Time.now - result = yield - logger.warn(exec_explain(queries)) if Time.now - start > threshold - result - ensure - current[:available_queries_for_explain] = nil - end - else - yield - end - end - - # Relation#explain needs to be able to collect the queries regardless of - # whether auto explain is enabled. This method serves that purpose. + # Relation#explain needs to be able to collect the queries. def collecting_queries_for_explain # :nodoc: current = Thread.current original, current[:available_queries_for_explain] = current[:available_queries_for_explain], [] @@ -68,20 +32,5 @@ module ActiveRecord end str end - - # Silences automatic EXPLAIN logging for the duration of the block. - # - # This has high priority, no EXPLAINs will be run even if downwards - # the threshold is set to 0. - # - # As the name of the method suggests this only applies to automatic - # EXPLAINs, manual calls to <tt>ActiveRecord::Relation#explain</tt> run. - def silence_auto_explain - current = Thread.current - original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false - yield - ensure - current[:available_queries_for_explain] = original - end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 066f93635a..347f023793 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -367,7 +367,16 @@ module ActiveRecord # # # triggers @brake.car.touch and @brake.car.corporation.touch # @brake.touch + # + # Note that +touch+ must be used on a persisted object, or else an + # ActiveRecordError will be thrown. For example: + # + # ball = Ball.new + # ball.touch(:updated_at) # => raises ActiveRecordError + # def touch(name = nil) + raise ActiveRecordError, "can not touch on a new record object" unless persisted? + attributes = timestamp_attributes_for_update_in_model attributes << name if name @@ -420,13 +429,22 @@ module ActiveRecord # Returns the number of affected rows. def update_record(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_with_values_for_update(attribute_names) - if attributes_with_values.empty? 0 else klass = self.class - stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values) - klass.connection.update stmt + column_hash = klass.connection.schema_cache.columns_hash klass.table_name + db_columns_with_values = attributes_with_values.map { |attr,value| + real_column = column_hash[attr.name] + [real_column, value] + } + bind_attrs = attributes_with_values.dup + bind_attrs.keys.each_with_index do |column, i| + real_column = db_columns_with_values[i].first + bind_attrs[column] = klass.connection.substitute_at(real_column, i) + end + stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(bind_attrs) + klass.connection.update stmt, 'SQL', db_columns_with_values end end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index f08b9c614d..e04a3d0976 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -33,18 +33,16 @@ module ActiveRecord # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date] # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...] def find_by_sql(sql, binds = []) - logging_query_plan do - result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) - column_types = {} + result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) + column_types = {} - if result_set.respond_to? :column_types - column_types = result_set.column_types - else - ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`" - end - - result_set.map { |record| instantiate(record, column_types) } + if result_set.respond_to? :column_types + column_types = result_set.column_types + else + ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`" end + + result_set.map { |record| instantiate(record, column_types) } end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. @@ -57,10 +55,8 @@ module ActiveRecord # # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" def count_by_sql(sql) - logging_query_plan do - sql = sanitize_conditions(sql) - connection.select_value(sql, "#{name} Count").to_i - end + sql = sanitize_conditions(sql) + connection.select_value(sql, "#{name} Count").to_i end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index a979be6999..64eac3aca7 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -112,7 +112,18 @@ module ActiveRecord `config/application.rb` file and any `mass_assignment_sanitizer` options from your `config/environments/*.rb` files. - See http://guides.rubyonrails.org/security.html#mass-assignment for more information + See http://guides.rubyonrails.org/security.html#mass-assignment for more information. + EOF + end + + unless app.config.active_record.delete(:auto_explain_threshold_in_seconds).nil? + ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, [] + The Active Record auto explain feature has been removed. + + To disable this message remove the `active_record.auto_explain_threshold_in_seconds` + option from the `config/environments/*.rb` config file. + + See http://guides.rubyonrails.org/4_0_release_notes.html for more information. EOF end @@ -124,7 +135,7 @@ module ActiveRecord To disable this message remove the `observers` option from your `config/application.rb` or from your initializers. - See http://guides.rubyonrails.org/4_0_release_notes.html for more information + See http://guides.rubyonrails.org/4_0_release_notes.html for more information. EOF end ensure @@ -146,13 +157,6 @@ module ActiveRecord end end - initializer "active_record.validate_explain_support" do |app| - if app.config.active_record[:auto_explain_threshold_in_seconds] && - !ActiveRecord::Base.connection.supports_explain? - warn "auto_explain_threshold_in_seconds is set but will be ignored because your adapter does not support this feature. Please unset the configuration to avoid this warning." - end - end - # Expose database runtime to controller for logging. initializer "active_record.log_runtime" do |app| require "active_record/railties/controller_runtime" diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb index 7695eacbff..af4840476c 100644 --- a/activerecord/lib/active_record/railties/controller_runtime.rb +++ b/activerecord/lib/active_record/railties/controller_runtime.rb @@ -21,9 +21,10 @@ module ActiveRecord def cleanup_view_runtime if ActiveRecord::Base.connected? db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime = (db_runtime || 0) + db_rt_before_render runtime = super db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime - self.db_runtime = db_rt_before_render + db_rt_after_render + self.db_runtime += db_rt_after_render runtime - db_rt_after_render else super diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 0053530f73..bc50802c4a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -444,17 +444,7 @@ module ActiveRecord # # Post.where(published: true).load # => #<ActiveRecord::Relation> def load - unless loaded? - # We monitor here the entire execution rather than individual SELECTs - # because from the point of view of the user fetching the records of a - # relation is a single unit of work. You want to know if this call takes - # too long, not if the individual queries take too long. - # - # It could be the case that none of the queries involved surpass the - # threshold, and at the same time the sum of them all does. The user - # should get a query plan logged in that case. - logging_query_plan { exec_queries } - end + exec_queries unless loaded? self end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 615309964c..00a506c3a7 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -12,7 +12,7 @@ module ActiveRecord delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, - :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass + :connection, :columns_hash, :to => :klass module ClassSpecificRelation extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 7ddaea1bb0..14520381c9 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -58,7 +58,7 @@ module ActiveRecord # order. The order will depend on the database implementation. # If an order is supplied it will be respected. # - # Person.take # returns an object fetched by SELECT * FROM people + # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1 # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5 # Person.where(["name LIKE '%?'", name]).take def take(limit = nil) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 5cd015eba7..bd783a94cf 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -98,11 +98,6 @@ module ActiveRecord when Class # FIXME: I think we need to deprecate this behavior attribute.eq(value.name) - when Integer, ActiveSupport::Duration - # Arel treats integers as literals, but they should be quoted when compared with strings - table = attribute.relation - column = table.engine.connection.schema_cache.columns_hash(table.name)[attribute.name.to_s] - attribute.eq(Arel::Nodes::SqlLiteral.new(table.engine.connection.quote(value, column))) else attribute.eq(value) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 20e23cb003..4b8c40592e 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -112,7 +112,7 @@ module ActiveRecord # # User.includes(:posts).where('posts.name = ?', 'example').references(:posts) def includes(*args) - has_arguments?("includes", args) + check_if_method_has_arguments!("includes", args) spawn.includes!(*args) end @@ -130,7 +130,7 @@ module ActiveRecord # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = # "users"."id" def eager_load(*args) - has_arguments?("eager_load", args) + check_if_method_has_arguments!("eager_load", args) spawn.eager_load!(*args) end @@ -144,7 +144,7 @@ module ActiveRecord # User.preload(:posts) # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3) def preload(*args) - has_arguments?("preload", args) + check_if_method_has_arguments!("preload", args) spawn.preload!(*args) end @@ -162,7 +162,7 @@ module ActiveRecord # User.includes(:posts).where("posts.name = 'foo'").references(:posts) # # => Query now knows the string references posts, so adds a JOIN def references(*args) - has_arguments?("references", args) + check_if_method_has_arguments!("references", args) spawn.references!(*args) end @@ -242,7 +242,7 @@ module ActiveRecord # User.group('name AS grouped_name, age') # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>] def group(*args) - has_arguments?("group", args) + check_if_method_has_arguments!("group", args) spawn.group!(*args) end @@ -273,7 +273,7 @@ module ActiveRecord # User.order(:name, email: :desc) # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC def order(*args) - has_arguments?("order", args) + check_if_method_has_arguments!("order", args) spawn.order!(*args) end @@ -285,6 +285,11 @@ module ActiveRecord references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! references!(references) if references.any? + # if a symbol is given we prepend the quoted table name + args = args.map { |arg| + arg.is_a?(Symbol) ? "#{quoted_table_name}.#{arg} ASC" : arg + } + self.order_values = args + self.order_values self end @@ -299,7 +304,7 @@ module ActiveRecord # # generates a query with 'ORDER BY name ASC, id ASC'. def reorder(*args) - has_arguments?("reorder", args) + check_if_method_has_arguments!("reorder", args) spawn.reorder!(*args) end @@ -322,7 +327,7 @@ module ActiveRecord # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id") # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id def joins(*args) - has_arguments?("joins", args) + check_if_method_has_arguments!("joins", args) spawn.joins!(*args.compact.flatten) end @@ -935,10 +940,10 @@ module ActiveRecord # passed into that method as an input. For example: # # def references(*args) - # has_arguments?("references", args) + # check_if_method_has_arguments!("references", args) # ... # end - def has_arguments?(method_name, args) + def check_if_method_has_arguments!(method_name, args) if args.blank? raise ArgumentError, "The method .#{method_name}() must contain arguments." end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 4b7a388dc7..33718ef0e9 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -218,9 +218,8 @@ module ActiveRecord # after_commit :do_bar, on: :update # after_commit :do_baz, on: :destroy # - # Also, to have the callback fired on create and update, but not on destroy: - # - # after_commit :do_zoo, if: :persisted? + # after_commit :do_foo_bar, :on [:create, :update] + # after_commit :do_bar_baz, :on [:update, :destroy] # # Note that transactional fixtures do not play well with this feature. Please # use the +test_after_commit+ gem to have these hooks fired in tests. @@ -244,12 +243,14 @@ module ActiveRecord if options.is_a?(Hash) && options[:on] assert_valid_transaction_action(options[:on]) options[:if] = Array(options[:if]) - options[:if] << "transaction_include_action?(:#{options[:on]})" + fire_on = Array(options[:on]).map(&:to_sym) + options[:if] << "transaction_include_any_action?(#{fire_on})" end end - def assert_valid_transaction_action(action) - unless ACTIONS.include?(action.to_sym) + def assert_valid_transaction_action(actions) + actions = Array(actions) + if (actions - ACTIONS).any? raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}" end end @@ -378,14 +379,16 @@ module ActiveRecord end # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. - def transaction_include_action?(action) #:nodoc: - case action - when :create - transaction_record_state(:new_record) - when :destroy - destroyed? - when :update - !(transaction_record_state(:new_record) || destroyed?) + def transaction_include_any_action?(actions) #:nodoc: + actions.any? do |action| + case action + when :create + transaction_record_state(:new_record) + when :destroy + destroyed? + when :update + !(transaction_record_state(:new_record) || destroyed?) + end end end end diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 0c35adc11d..c0471bb506 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -3,7 +3,7 @@ module ActiveRecord MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index f9149c1819..0af7cbf74f 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "models/book" module ActiveRecord class AdapterTest < ActiveRecord::TestCase @@ -6,6 +7,19 @@ module ActiveRecord @connection = ActiveRecord::Base.connection end + ## + # PostgreSQL does not support null bytes in strings + unless current_adapter?(:PostgreSQLAdapter) + def test_update_prepared_statement + b = Book.create(name: "my \x00 book") + b.reload + assert_equal "my \x00 book", b.name + b.update_attributes(name: "my other \x00 book") + b.reload + assert_equal "my other \x00 book", b.name + end + end + def test_tables tables = @connection.tables assert tables.include?("accounts") diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index b67d70ede7..b965983fec 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -19,6 +19,9 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_connect_with_url run_without_connection do |orig| ar_config = ARTest.connection_config['arunit'] + + skip "This test doesn't work with custom socket location" if ar_config['socket'] + url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" Klass.establish_connection(url) assert_equal ar_config['database'], Klass.connection.current_database diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 6640f9b497..ad98d7c8ce 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -65,6 +65,21 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase assert_equal :hstore, @column.type end + def test_change_table_supports_hstore + @connection.transaction do + @connection.change_table('hstores') do |t| + t.hstore 'users', default: '' + end + Hstore.reset_column_information + column = Hstore.columns.find { |c| c.name == 'users' } + assert_equal :hstore, column.type + + raise ActiveRecord::Rollback # reset the schema change + end + ensure + Hstore.reset_column_information + end + def test_type_cast_hstore assert @column diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index d64037eec0..6fc08ae4f0 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -31,6 +31,21 @@ class PostgresqlJSONTest < ActiveRecord::TestCase assert_equal :json, @column.type end + def test_change_table_supports_json + @connection.transaction do + @connection.change_table('json_data_type') do |t| + t.json 'users', default: '{}' + end + JsonDataType.reset_column_information + column = JsonDataType.columns.find { |c| c.name == 'users' } + assert_equal :json, column.type + + raise ActiveRecord::Rollback # reset the schema change + end + ensure + JsonDataType.reset_column_information + end + def test_type_cast_json assert @column diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 3bf9125013..1de7ee0846 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -73,6 +73,11 @@ class EagerAssociationTest < ActiveRecord::TestCase end end + def test_has_many_through_with_order + authors = Author.includes(:favorite_authors).to_a + assert_no_queries { authors.map(&:favorite_authors) } + end + def test_with_two_tables_in_from_without_getting_double_quoted posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a assert_equal 2, posts.first.comments.size @@ -213,7 +218,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once post = posts(:welcome) post.update!(author: nil) - post = assert_queries(1) { Post.all.merge!(includes: {author_with_address: :author_address}).find(post.id) } + post = assert_queries(1) { Post.all.merge!(includes: {author_with_address: :author_address}).find(post.id) } # find the post, then find the author which is null so no query for the author or address assert_no_queries do assert_equal nil, post.author_with_address @@ -1168,4 +1173,9 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size } assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" } end + + test "works in combination with order(:symbol)" do + author = Author.includes(:posts).references(:posts).order(:name).where('posts.title IS NOT NULL').first + assert_equal authors(:bob), author + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index fd6d531645..1ddd380f23 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -20,6 +20,8 @@ require 'models/car' require 'models/bulb' require 'models/engine' require 'models/categorization' +require 'models/minivan' +require 'models/speedometer' class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase class Invoice < ActiveRecord::Base @@ -1747,4 +1749,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase david.posts_with_special_categorizations = [] assert_equal [], david.posts_with_special_categorizations end + + test "does not duplicate associations when used with natural primary keys" do + speedometer = Speedometer.create!(id: '4') + speedometer.minivans.create!(minivan_id: 'a-van-red' ,name: 'a van', color: 'red') + + assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}" + assert_equal 1, speedometer.reload.minivans.to_a.size + end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index fbc66540d6..af1845c937 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1447,6 +1447,13 @@ class BasicsTest < ActiveRecord::TestCase assert_match(/\/#{dev.id}$/, dev.cache_key) end + def test_touch_should_raise_error_on_a_new_object + company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals") + assert_raises(ActiveRecord::ActiveRecordError) do + company.touch :updated_at + end + end + def test_cache_key_format_is_precise_enough dev = Developer.first key = dev.cache_key diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 0a04117795..c8dfc3244b 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -43,27 +43,6 @@ module ActiveRecord encoding: "utf8" }, spec) end - def test_url_query_numeric - spec = resolve 'abstract://foo:123?encoding=utf8&int=500&float=10.9' - assert_equal({ - adapter: "abstract", - port: 123, - int: 500, - float: 10.9, - host: "foo", - encoding: "utf8" }, spec) - end - - def test_url_query_boolean - spec = resolve 'abstract://foo:123?true=true&false=false' - assert_equal({ - adapter: "abstract", - port: 123, - true: true, - false: false, - host: "foo" }, spec) - end - def test_encoded_password password = 'am@z1ng_p@ssw0rd#!' encoded_password = URI.encode_www_form_component(password) diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index eca500f7e4..fe105b9d22 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -123,5 +123,14 @@ module ActiveRecord assert duped.valid? end end + + def test_dup_with_default_scope + prev_default_scopes = Topic.default_scopes + Topic.default_scopes = [Topic.where(:approved => true)] + topic = Topic.new(:approved => false) + assert !topic.dup.approved?, "should not be overriden by default scopes" + ensure + Topic.default_scopes = prev_default_scopes + end end end diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index aa2a6d7509..b1d276f9eb 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -14,46 +14,9 @@ if ActiveRecord::Base.connection.supports_explain? base.connection end - def test_logging_query_plan_with_logger - base.logger.expects(:warn).with do |message| - message.starts_with?('EXPLAIN for:') - end - - with_threshold(0) do - Car.where(:name => 'honda').to_a - end - end - - def test_logging_query_plan_without_logger - original = base.logger - base.logger = nil - - class << base.logger - def warn; raise "Should not be called" end - end - - with_threshold(0) do - car = Car.where(:name => 'honda').first - assert_equal 'honda', car.name - end - ensure - base.logger = original - end - - def test_collect_queries_for_explain - base.auto_explain_threshold_in_seconds = nil - queries = Thread.current[:available_queries_for_explain] = [] - - with_threshold(0) do - Car.where(:name => 'honda').to_a - end - - sql, binds = queries[0] - assert_match "SELECT", sql - assert_match "honda", sql - assert_equal [], binds - ensure - Thread.current[:available_queries_for_explain] = nil + def test_relation_explain + message = Car.where(:name => 'honda').explain + assert_match(/^EXPLAIN for:/, message) end def test_collecting_queries_for_explain @@ -68,16 +31,6 @@ if ActiveRecord::Base.connection.supports_explain? assert_equal [cars(:honda)], result end - def test_logging_query_plan_when_counting_by_sql - base.logger.expects(:warn).with do |message| - message.starts_with?('EXPLAIN for:') - end - - with_threshold(0) do - Car.count_by_sql "SELECT COUNT(*) FROM cars WHERE name = 'honda'" - end - end - def test_exec_explain_with_no_binds sqls = %w(foo bar) binds = [[], []] @@ -113,25 +66,8 @@ if ActiveRecord::Base.connection.supports_explain? base.logger.expects(:warn).never - with_threshold(0) do - Car.where(:name => 'honda').to_a - end - end - - def test_silence_auto_explain - base.expects(:collecting_sqls_for_explain).never - base.logger.expects(:warn).never - base.silence_auto_explain do - with_threshold(0) { Car.all.to_a } - end + Car.where(:name => 'honda').to_a end - def with_threshold(threshold) - current_threshold = base.auto_explain_threshold_in_seconds - base.auto_explain_threshold_in_seconds = threshold - yield - ensure - base.auto_explain_threshold_in_seconds = current_threshold - end end end diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/columns_test.rb index 88bea2211d..e52809f0f8 100644 --- a/activerecord/test/cases/migration/rename_column_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -2,7 +2,7 @@ require "cases/migration/helper" module ActiveRecord class Migration - class RenameColumnTest < ActiveRecord::TestCase + class ColumnsTest < ActiveRecord::TestCase include ActiveRecord::Migration::TestHelper self.use_transactional_fixtures = false @@ -96,10 +96,18 @@ module ActiveRecord add_index "test_models", ["hat_style", "hat_size"], unique: true rename_column "test_models", "hat_size", 'size' - assert_equal ['index_test_models_on_hat_style_and_size'], connection.indexes('test_models').map(&:name) + if current_adapter? :OracleAdapter + assert_equal ['i_test_models_hat_style_size'], connection.indexes('test_models').map(&:name) + else + assert_equal ['index_test_models_on_hat_style_and_size'], connection.indexes('test_models').map(&:name) + end rename_column "test_models", "hat_style", 'style' - assert_equal ['index_test_models_on_style_and_size'], connection.indexes('test_models').map(&:name) + if current_adapter? :OracleAdapter + assert_equal ['i_test_models_style_size'], connection.indexes('test_models').map(&:name) + else + assert_equal ['index_test_models_on_style_and_size'], connection.indexes('test_models').map(&:name) + end end def test_rename_column_does_not_rename_custom_named_index @@ -128,7 +136,7 @@ module ActiveRecord assert_equal 1, connection.indexes('test_models').size remove_column("test_models", "hat_size") - # Every database and/or database adapter has their own behavior + # Every database and/or database adapter has their own behavior # if it drops the multi-column index when any of the indexed columns dropped by remove_column. if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) assert_equal [], connection.indexes('test_models').map(&:name) @@ -245,6 +253,20 @@ module ActiveRecord def test_remove_column_no_second_parameter_raises_exception assert_raise(ArgumentError) { connection.remove_column("funny") } end + + def test_removing_and_renaming_column_preserves_custom_primary_key + connection.create_table "my_table", primary_key: "my_table_id", force: true do |t| + t.integer "col_one" + t.string "col_two", limit: 128, null: false + end + + remove_column("my_table", "col_two") + rename_column("my_table", "col_one", "col_three") + + assert_equal 'my_table_id', connection.primary_key('my_table') + ensure + connection.drop_table(:my_table) rescue nil + end end end end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 0ad05223d4..3dd11ae89d 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -122,35 +122,35 @@ module ActiveRecord def test_quote_float float = 1.2 assert_equal float.to_s, @quoter.quote(float, nil) - assert_equal float.to_s, @quoter.quote(float, FakeColumn.new(:float)) + assert_equal float.to_s, @quoter.quote(float, Object.new) end def test_quote_fixnum fixnum = 1 assert_equal fixnum.to_s, @quoter.quote(fixnum, nil) - assert_equal fixnum.to_s, @quoter.quote(fixnum, FakeColumn.new(:integer)) + assert_equal fixnum.to_s, @quoter.quote(fixnum, Object.new) end def test_quote_bignum bignum = 1 << 100 assert_equal bignum.to_s, @quoter.quote(bignum, nil) - assert_equal bignum.to_s, @quoter.quote(bignum, FakeColumn.new(:integer)) + assert_equal bignum.to_s, @quoter.quote(bignum, Object.new) end def test_quote_bigdecimal bigdec = BigDecimal.new((1 << 100).to_s) assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, nil) - assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, FakeColumn.new(:decimal)) + assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, Object.new) end def test_dates_and_times @quoter.extend(Module.new { def quoted_date(value) 'lol' end }) assert_equal "'lol'", @quoter.quote(Date.today, nil) - assert_equal "'lol'", @quoter.quote(Date.today, FakeColumn.new(:date)) + assert_equal "'lol'", @quoter.quote(Date.today, Object.new) assert_equal "'lol'", @quoter.quote(Time.now, nil) - assert_equal "'lol'", @quoter.quote(Time.now, FakeColumn.new(:time)) + assert_equal "'lol'", @quoter.quote(Time.now, Object.new) assert_equal "'lol'", @quoter.quote(DateTime.now, nil) - assert_equal "'lol'", @quoter.quote(DateTime.now, FakeColumn.new(:datetime)) + assert_equal "'lol'", @quoter.quote(DateTime.now, Object.new) end def test_crazy_object diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 53cdf89b1f..c43c7601a2 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -108,30 +108,5 @@ module ActiveRecord assert_equal 4, Edge.where(blank).order("sink_id").to_a.size end end - - def test_where_with_integer_for_string_column - count = Post.where(:title => 0).count - assert_equal 0, count - end - - def test_where_with_float_for_string_column - count = Post.where(:title => 0.0).count - assert_equal 0, count - end - - def test_where_with_boolean_for_string_column - count = Post.where(:title => false).count - assert_equal 0, count - end - - def test_where_with_decimal_for_string_column - count = Post.where(:title => BigDecimal.new(0)).count - assert_equal 0, count - end - - def test_where_with_duration_for_string_column - count = Post.where(:title => 0.seconds).count - assert_equal 0, count - end end end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 8e6c38706f..7388324a0d 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -391,19 +391,19 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scope_with_inheritance wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_default_scope_with_module_includes wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_scope_overwrites_default diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 92dc575d37..fd0b05cb77 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -180,19 +180,32 @@ module ActiveRecord class RelationMutationTest < ActiveSupport::TestCase class FakeKlass < Struct.new(:table_name, :name) + def quoted_table_name + %{"#{table_name}"} + end end def relation - @relation ||= Relation.new FakeKlass, :b + @relation ||= Relation.new FakeKlass.new('posts'), :b end - (Relation::MULTI_VALUE_METHODS - [:references, :extending]).each do |method| + (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal [:foo], relation.public_send("#{method}_values") end end + test "#order!" do + assert relation.order!('name ASC').equal?(relation) + assert_equal ['name ASC'], relation.order_values + end + + test "#order! with symbol prepends the table name" do + assert relation.order!(:name).equal?(relation) + assert_equal ['"posts".name ASC'], relation.order_values + end + test '#references!' do assert relation.references!(:foo).equal?(relation) assert relation.references_values.include?('foo') diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index bfecc0d1e9..1147b9a09e 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -261,22 +261,22 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_includes_inet_shorthand_definition output = standard_dump - if %r{create_table "postgresql_network_address"} =~ output - assert_match %r{t.inet "inet_address"}, output + if %r{create_table "postgresql_network_addresses"} =~ output + assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output end end def test_schema_dump_includes_cidr_shorthand_definition output = standard_dump - if %r{create_table "postgresql_network_address"} =~ output - assert_match %r{t.cidr "cidr_address"}, output + if %r{create_table "postgresql_network_addresses"} =~ output + assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output end end def test_schema_dump_includes_macaddr_shorthand_definition output = standard_dump - if %r{create_table "postgresql_network_address"} =~ output - assert_match %r{t.macaddr "macaddr_address"}, output + if %r{create_table "postgresql_network_addresses"} =~ output + assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output end end diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index bb034848e1..777a2b70dd 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -113,6 +113,18 @@ class TimestampTest < ActiveRecord::TestCase assert_not_equal previously_owner_updated_at, pet.owner.updated_at end + def test_saving_a_new_record_belonging_to_invalid_parent_with_touch_should_not_raise_exception + klass = Class.new(Owner) do + def self.name; 'Owner'; end + validate { errors.add(:base, :invalid) } + end + + pet = Pet.new(owner: klass.new) + pet.save! + + assert pet.owner.new_record? + end + def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute klass = Class.new(ActiveRecord::Base) do def self.name; 'Pet'; end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index fd5651b4e0..eb4ffd4498 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -312,3 +312,38 @@ class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase assert_equal true, topic.record_updated end end + +class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class TopicWithCallbacksOnMultipleActions < ActiveRecord::Base + self.table_name = :topics + + after_commit(on: [:create, :destroy]) { |record| record.history << :create_and_destroy } + after_commit(on: [:create, :update]) { |record| record.history << :create_and_update } + after_commit(on: [:update, :destroy]) { |record| record.history << :update_and_destroy } + + def clear_history + @history = [] + end + + def history + @history ||= [] + end + end + + def test_after_commit_on_multiple_actions + topic = TopicWithCallbacksOnMultipleActions.new + topic.save + assert_equal [:create_and_update, :create_and_destroy], topic.history + + topic.clear_history + topic.approved = true + topic.save + assert_equal [:update_and_destroy, :create_and_update], topic.history + + topic.clear_history + topic.destroy + assert_equal [:update_and_destroy, :create_and_destroy], topic.history + end +end diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb index 0a7d38d8ec..497c3aba9a 100644 --- a/activerecord/test/models/speedometer.rb +++ b/activerecord/test/models/speedometer.rb @@ -1,4 +1,6 @@ class Speedometer < ActiveRecord::Base self.primary_key = :speedometer_id belongs_to :dashboard + + has_many :minivans end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 83b50030bd..d8271ac8d1 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -145,9 +145,9 @@ _SQL execute <<_SQL CREATE TABLE postgresql_network_addresses ( id SERIAL PRIMARY KEY, - cidr_address CIDR, - inet_address INET, - mac_address MACADDR + cidr_address CIDR default '192.168.1.0/24', + inet_address INET default '192.168.1.1', + mac_address MACADDR default 'ff:ff:ff:ff:ff:ff' ); _SQL diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index d789b6cb7a..cd9835259a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -540,8 +540,6 @@ ActiveRecord::Schema.define do create_table :price_estimates, :force => true do |t| t.string :estimate_of_type t.integer :estimate_of_id - t.string :thing_type - t.integer :thing_id t.integer :price end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index daa283e397..c47cb75274 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,17 +1,60 @@ ## Rails 4.0.0 (unreleased) ## +* Fix deletion of empty directories in ActiveSupport::Cache::FileStore. + + *Charles Jones* + +## Rails 4.0.0.beta1 (February 25, 2013) ## + +* Prevent `DateTime#change` from truncating the second fraction, when seconds + do not need to be changed. + + *Chris Baynes* + +* Added `ActiveSupport::TimeWithZone#to_r` for `Time#at` compatibility. + + Before this change: + + Time.zone = 'Tokyo' + time = Time.zone.now + time == Time.at(time) # => false + + After the change: + + Time.zone = 'Tokyo' + time = Time.zone.now + time == Time.at(time) # => true + + *stopdropandrew* + +* `ActiveSupport::NumberHelper#number_to_human` returns the number unaltered when + the given units hash does not contain the needed key, e.g. when the number provided + is less than the largest key provided. + Fixes #9269. + + Examples: + + number_to_human(123, units: {}) # => 123 + number_to_human(123, units: { thousand: 'k' }) # => 123 + + *Michael Hoffman* + +* Added `beginning_of_minute` support to core ext calculations for `Time` and `DateTime`. + + *Gagan Awhad* + * Add `:nsec` date format. *Jamie Gaskins* -* ActiveSupport::Gzip.compress allows two optional arguments for compression +* `ActiveSupport::Gzip.compress` allows two optional arguments for compression level and strategy. *Beyond* * Modify `TimeWithZone#as_json` to include 3 decimal places of sub-second accuracy by default, which is optional as per the ISO8601 spec, but extremely useful. Also - the default behaviour of Date#toJSON() in recent versions of Chrome, Safari and + the default behaviour of `Date#toJSON()` in recent versions of Chrome, Safari and Firefox. *James Harton* @@ -24,7 +67,7 @@ *Andrew White* * Extract `ActiveSupport::Testing::Performance` into https://github.com/rails/rails-perftest - You can add the gem to your Gemfile to keep using performance tests. + You can add the gem to your `Gemfile` to keep using performance tests. gem 'rails-perftest' @@ -51,7 +94,7 @@ *Kelly Stannard* * It's now possible to compare `Date`, `DateTime`, `Time` and `TimeWithZone` - with `Infinity`. This allows to create date/time ranges with one infinite bound. + with `Float::INFINITY`. This allows to create date/time ranges with one infinite bound. Example: range = Range.new(Date.today, Float::INFINITY) @@ -84,13 +127,13 @@ * Remove surrogate unicode character encoding from `ActiveSupport::JSON.encode` The encoding scheme was broken for unicode characters outside the basic multilingual plane; - since json is assumed to be `UTF-8`, and we already force the encoding to `UTF-8`, + since json is assumed to be UTF-8, and we already force the encoding to UTF-8, simply pass through the un-encoded characters. *Brett Carter* * Deprecate `Time.time_with_date_fallback`, `Time.utc_time` and `Time.local_time`. - These methods were added to handle the limited range of Ruby's native Time + These methods were added to handle the limited range of Ruby's native `Time` implementation. Those limitations no longer apply so we are deprecating them in 4.0 and they will be removed in 4.1. @@ -98,16 +141,17 @@ * Deprecate `Date#to_time_in_current_zone` and add `Date#in_time_zone`. *Andrew White* -* Add `String#in_time_zone` method to convert a string to an ActiveSupport::TimeWithZone. *Andrew White* +* Add `String#in_time_zone` method to convert a string to an `ActiveSupport::TimeWithZone`. *Andrew White* * Deprecate `ActiveSupport::BasicObject` in favor of `ActiveSupport::ProxyObject`. - This class is used for proxy classes. It avoids confusion with Ruby's BasicObject + This class is used for proxy classes. It avoids confusion with Ruby's `BasicObject` class. *Francesco Rodriguez* -* Patched Marshal#load to work with constant autoloading. - Fixes autoloading with cache stores that relay on Marshal(MemCacheStore and FileStore). [fixes #8167] +* Patched `Marshal#load` to work with constant autoloading. Fixes autoloading + with cache stores that rely on `Marshal` (`MemCacheStore` and `FileStore`). + Fixes #8167. *Uriel Katz* @@ -122,9 +166,9 @@ *Olek Janiszewski* -* No longer proxy ActiveSupport::Multibyte#class. *Steve Klabnik* +* No longer proxy `ActiveSupport::Multibyte#class`. *Steve Klabnik* -* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. *Carlos Antonio da Silva* +* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from minitest instead. *Carlos Antonio da Silva* * `XmlMini.with_backend` now may be safely used with threads: @@ -139,18 +183,17 @@ *Nikita Afanasenko* -* Dependencies no longer trigger Kernel#autoload in remove_constant [fixes #8213]. *Xavier Noria* +* Dependencies no longer trigger `Kernel#autoload` in `remove_constant`. Fixes #8213. *Xavier Noria* -* Simplify mocha integration and remove monkey-patches, bumping mocha to 0.13.0. *James Mead* +* Simplify `mocha` integration and remove monkey-patches, bumping `mocha` to 0.13.0. *James Mead* -* `#as_json` isolates options when encoding a hash. - Fix #8182 +* `#as_json` isolates options when encoding a hash. Fixes #8182. *Yves Senn* -* Deprecate Hash#diff in favor of MiniTest's #diff. *Steve Klabnik* +* Deprecate `Hash#diff` in favor of minitest's #diff. *Steve Klabnik* -* Kernel#capture can catch output from subprocesses *Dmitry Vorotilin* +* `Kernel#capture` can catch output from subprocesses. *Dmitry Vorotilin* * `to_xml` conversions now use builder's `tag!` method instead of explicit invocation of `method_missing`. @@ -158,27 +201,30 @@ * Fixed timezone mapping of the Solomon Islands. *Steve Klabnik* -* Make callstack attribute optional in - ActiveSupport::Deprecation::Reporting methods `warn` and `deprecation_warning` +* Make callstack attribute optional in `ActiveSupport::Deprecation::Reporting` + methods `warn` and `deprecation_warning`. *Alexey Gaziev* -* Implement HashWithIndifferentAccess#replace so key? works correctly. *David Graham* +* Implement `HashWithIndifferentAccess#replace` so `key?` works correctly. *David Graham* -* Handle the possible Permission Denied errors atomic.rb might trigger due to its chown and chmod calls. *Daniele Sluijters* +* Handle the possible permission denied errors `atomic.rb` might trigger due to its `chown` + and `chmod` calls. -* Hash#extract! returns only those keys that present in the receiver. + *Daniele Sluijters* + +* `Hash#extract!` returns only those keys that present in the receiver. {a: 1, b: 2}.extract!(:a, :x) # => {:a => 1} *Mikhail Dieterle* -* Hash#extract! returns the same subclass, that the receiver is. I.e. - HashWithIndifferentAccess#extract! returns HashWithIndifferentAccess instance. +* `Hash#extract!` returns the same subclass, that the receiver is. I.e. + `HashWithIndifferentAccess#extract!` returns a `HashWithIndifferentAccess` instance. *Mikhail Dieterle* -* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. *Brian Durand* +* Optimize `ActiveSupport::Cache::Entry` to reduce memory and processing overhead. *Brian Durand* * Tests tag the Rails log with the current test class and test case: @@ -187,7 +233,7 @@ *Jeremy Kemper* -* Add `logger.push_tags` and `.pop_tags` to complement logger.tagged: +* Add `logger.push_tags` and `.pop_tags` to complement `logger.tagged`: class Job def before @@ -223,6 +269,7 @@ class User include ActiveSupport::Configurable + config_accessor :hair_colors do [:brown, :black, :blonde, :red] end @@ -232,7 +279,7 @@ *Larry Lv* -* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of +* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. *Steve Klabnik* @@ -242,14 +289,14 @@ *Pranas Kiziela* -* ActiveSupport::Deprecation is now a class. It is possible to create an instance +* `ActiveSupport::Deprecation` is now a class. It is possible to create an instance of deprecator. Backwards compatibility has been preserved. You can choose which instance of the deprecator will be used. deprecate :method_name, deprecator: deprecator_instance - You can use ActiveSupport::Deprecation in your gem. + You can use `ActiveSupport::Deprecation` in your gem. require 'active_support/deprecation' require 'active_support/core_ext/module/deprecation' @@ -276,7 +323,7 @@ * `ERB::Util.html_escape` encodes single quote as `#39`. Decimal form has better support in old browsers. *Kalys Osmonov* * `ActiveSupport::Callbacks`: deprecate monkey patch of object callbacks. - Using the #filter method like this: + Using the `filter` method like this: before_filter MyFilter.new @@ -309,7 +356,7 @@ *Akira Matsuda* -* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore +* Replace deprecated `memcache-client` gem with `dalli` in `ActiveSupport::Cache::MemCacheStore`. *Guillermo Iguaran* @@ -323,14 +370,14 @@ *Erich Menge* -* Add String#indent. *fxn & Ace Suares* +* Add `String#indent`. *fxn & Ace Suares* * Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. *David Celis* -* `Object#try` will now return nil instead of raise a NoMethodError if the +* `Object#try` will now return `nil` instead of raise a `NoMethodError` if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. @@ -358,62 +405,64 @@ *Francesco Rodriguez* -* ActionView::Helpers::NumberHelper methods have been moved to ActiveSupport::NumberHelper and are now available via - Numeric#to_s. Numeric#to_s now accepts the formatting options :phone, :currency, :percentage, :delimited, - :rounded, :human, and :human_size. *Andrew Mutz* +* `ActionView::Helpers::NumberHelper` methods have been moved to `ActiveSupport::NumberHelper` and are now available via + `Numeric#to_s`. `Numeric#to_s` now accepts the formatting options `:phone`, `:currency`, `:percentage`, `:delimited`, + `:rounded`, `:human`, and `:human_size`. + + *Andrew Mutz* * 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* +* 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_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* +* 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* -* `deep_dup` works more expectedly now and duplicates also values in +Hash+ instances and elements in +Array+ instances. *Alexey Gaziev* +* `deep_dup` works more expectedly now and duplicates also values in `Hash` instances and elements in `Array` instances. *Alexey Gaziev* -* Inflector no longer applies ice -> ouse to words like slice, police, ets *Wes Morgan* +* Inflector no longer applies ice -> ouse to words like "slice", "police", etc. *Wes Morgan* -* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations *twinturbo* +* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations. *twinturbo* -* Make Module#delegate stop using `send` - can no longer delegate to private methods. *dasch* +* Make `Module#delegate` stop using `send` - can no longer delegate to private methods. *dasch* -* AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev* +* `ActiveSupport::Callbacks`: deprecate `:rescuable` option. *Bogdan Gusiev* -* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea* +* Adds `Integer#ordinal` to get the ordinal suffix string of an integer. *Tim Gildea* -* AS::Callbacks: `:per_key` option is no longer supported +* `ActiveSupport::Callbacks`: `:per_key` option is no longer supported. *Bogdan Gusiev* -* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. +* `ActiveSupport::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. *Bogdan Gusiev* -* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva* +* Add `html_escape_once` to `ERB::Util`, and delegate the `escape_once` tag helper to it. *Carlos Antonio da Silva* -* Deprecates the compatibility method Module#local_constant_names, - use Module#local_constants instead (which returns symbols). *fxn* +* Deprecates the compatibility method `Module#local_constant_names`, + use `Module#local_constants` instead (which returns symbols). *Xavier Noria* -* Deletes the compatibility method Module#method_names, - use Module#methods from now on (which returns symbols). *fxn* +* Deletes the compatibility method `Module#method_names`, + use `Module#methods` from now on (which returns symbols). *Xavier Noria* -* Deletes the compatibility method Module#instance_method_names, - use Module#instance_methods from now on (which returns symbols). *fxn* +* Deletes the compatibility method `Module#instance_method_names`, + use `Module#instance_methods` from now on (which returns symbols). *Xavier Noria* -* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger - from Ruby stdlib. +* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger + from the Ruby standard library. -* Unicode database updated to 6.1.0. + *Aaron Patterson* -* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead - of wrapping them in strings for safety. +* Unicode database updated to 6.1.0. *Norman Clarke* -* Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge* +* Adds `encode_big_decimal_as_string` option to force JSON serialization of `BigDecimal` as numeric instead + of wrapping them in strings for safety. * Optimize log subscribers to check log level before doing any processing. *Brian Durand* diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc index ed688ecc59..f3582767c0 100644 --- a/activesupport/README.rdoc +++ b/activesupport/README.rdoc @@ -12,7 +12,7 @@ The latest version of Active Support can be installed with RubyGems: % [sudo] gem install activesupport -Source code can be downloaded as part of the Rails project on GitHub +Source code can be downloaded as part of the Rails project on GitHub: * https://github.com/rails/rails/tree/master/activesupport @@ -26,7 +26,7 @@ Active Support is released under the MIT license: == Support -API documentation is at +API documentation is at: * http://api.rubyonrails.org diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 64b23996e8..a28310032a 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] - s.add_dependency 'i18n', '~> 0.6' + s.add_dependency('i18n', '~> 0.6', '>= 0.6.4') s.add_dependency 'multi_json', '~> 1.3' s.add_dependency 'tzinfo', '~> 0.3.33' s.add_dependency 'minitest', '~> 4.2' diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 8e265ad863..0c55aa8a32 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -150,9 +150,9 @@ module ActiveSupport # Delete empty directories in the cache. def delete_empty_directories(dir) - return if dir == cache_path + return if File.realpath(dir) == File.realpath(cache_path) if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty? - File.delete(dir) rescue nil + Dir.delete(dir) rescue nil delete_empty_directories(File.dirname(dir)) end end diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 1d3682eaf2..9f0864d9bb 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -59,7 +59,7 @@ class DateTime options.fetch(:day, day), options.fetch(:hour, hour), options.fetch(:min, options[:hour] ? 0 : min), - options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction), options.fetch(:offset, offset), options.fetch(:start, start) ) @@ -124,6 +124,18 @@ class DateTime end alias :at_end_of_hour :end_of_hour + # Returns a new DateTime representing the start of the minute (hh:mm:00). + def beginning_of_minute + change(:sec => 0) + end + alias :at_beginning_of_minute :beginning_of_minute + + # Returns a new DateTime representing the end of the minute (hh:mm:59). + def end_of_minute + change(:sec => 59) + end + alias :at_end_of_minute :end_of_minute + # Adjusts DateTime to UTC by adding its offset value; offset is set to 0. # # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600 diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 1f95f62229..a3ce7dbe3f 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -188,6 +188,21 @@ class Time end alias :at_end_of_hour :end_of_hour + # Returns a new Time representing the start of the minute (x:xx:00) + def beginning_of_minute + change(:sec => 0) + end + alias :at_beginning_of_minute :beginning_of_minute + + # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9) + def end_of_minute + change( + :sec => 59, + :usec => Rational(999999999, 1000) + ) + end + alias :at_end_of_minute :end_of_minute + # Returns a Range representing the whole day of the current time. def all_day beginning_of_day..end_of_day diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 2191471daa..cc935e6cb9 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -580,7 +580,7 @@ module ActiveSupport unit = case units when Hash - units[DECIMAL_UNITS[display_exponent]] + units[DECIMAL_UNITS[display_exponent]] || '' when String, Symbol I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) else diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 0e6d12a186..98c866ac43 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -317,6 +317,10 @@ module ActiveSupport end alias_method :tv_sec, :to_i + def to_r + utc.to_r + end + # Return an instance of Time in the system timezone. def to_time utc.to_time diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 8a8f8f946d..ec0967fdd7 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -3,7 +3,7 @@ module ActiveSupport MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 5158bbc196..ede08e23d5 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -672,6 +672,18 @@ class FileStoreTest < ActiveSupport::TestCase end end + def test_delete_does_not_delete_empty_parent_dir + sub_cache_dir = File.join(cache_dir, 'subdir/') + sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir) + assert_nothing_raised(Exception) do + assert sub_cache_store.write('foo', 'bar') + assert sub_cache_store.delete('foo') + end + assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!" + assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!" + assert Dir.entries(sub_cache_dir).reject {|f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f)}.empty? + end + def test_log_exception_when_cache_read_fails File.expects(:exist?).raises(StandardError, "failed") @cache.send(:read_entry, "winston", {}) diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 24e62cc2b9..7be578599b 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -88,6 +88,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour end + def test_beginning_of_minute + assert_equal DateTime.civil(2005,2,4,19,30,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_minute + end + + def test_end_of_minute + assert_equal DateTime.civil(2005,2,4,19,30,59), DateTime.civil(2005,2,4,19,30,10).end_of_minute + end + def test_end_of_month assert_equal DateTime.civil(2005,3,31,23,59,59), DateTime.civil(2005,3,20,10,10,10).end_of_month assert_equal DateTime.civil(2005,2,28,23,59,59), DateTime.civil(2005,2,20,10,10,10).end_of_month @@ -121,6 +129,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2005,2,22,16), DateTime.civil(2005,2,22,15,15,10).change(:hour => 16) assert_equal DateTime.civil(2005,2,22,16,45), DateTime.civil(2005,2,22,15,15,10).change(:hour => 16, :min => 45) assert_equal DateTime.civil(2005,2,22,15,45), DateTime.civil(2005,2,22,15,15,10).change(:min => 45) + + # datetime with fractions of a second + assert_equal DateTime.civil(2005,2,1,15,15,10.7), DateTime.civil(2005,2,22,15,15,10.7).change(:day => 1) end def test_advance diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 43c92003dc..2864d7a57f 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -121,6 +121,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour end + def test_beginning_of_minute + assert_equal Time.local(2005,2,4,19,30,0), Time.local(2005,2,4,19,30,10).beginning_of_minute + end + def test_end_of_day assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day with_env_tz 'US/Eastern' do @@ -137,6 +141,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour end + def test_end_of_minute + assert_equal Time.local(2005,2,4,19,30,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_minute + end + def test_last_year assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index c2b3676aac..0f5699fd63 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -326,6 +326,17 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal 946684800, twz.to_i end + def test_to_r + result = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']).to_r + assert_equal Rational(946684800, 1), result + assert_kind_of Rational, result + end + + def test_time_at + time = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']) + assert_equal time, Time.at(time) + end + def test_to_time with_env_tz 'US/Eastern' do assert_equal Time, @twz.to_time.class @@ -539,6 +550,20 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect end + def test_beginning_of_minute + utc = Time.utc(2000, 1, 1, 0, 30, 10) + twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) + assert_equal "Fri, 31 Dec 1999 19:30:10 EST -05:00", twz.inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect + end + + def test_end_of_minute + utc = Time.utc(2000, 1, 1, 0, 30, 10) + twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) + assert_equal "Fri, 31 Dec 1999 19:30:10 EST -05:00", twz.inspect + assert_equal "Fri, 31 Dec 1999 19:30:59 EST -05:00", twz.end_of_minute.inspect + end + def test_since assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index 5f54587f93..1fadef3637 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -301,6 +301,13 @@ module ActiveSupport end end + def test_number_to_human_with_custom_units_that_are_missing_the_needed_key + [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| + assert_equal '123', number_helper.number_to_human(123, units: { thousand: 'k'}) + assert_equal '123', number_helper.number_to_human(123, units: {}) + end + end + def test_number_to_human_with_custom_format [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| assert_equal '123 times Thousand', number_helper.number_to_human(123456, :format => "%n times %u") diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index e9f7ff9d68..ee6e64b731 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,5 +1,8 @@ ## Rails 4.0.0 (unreleased) ## + +## Rails 4.0.0.beta1 (unreleased) ## + * Split Validations and Callbacks guide into two. *Steve Klabnik* * New guide _Working with JavaScript in Rails_. *Steve Klabnik* diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb index ed2667ac58..d169e9452c 100644 --- a/guides/code/getting_started/config/environments/development.rb +++ b/guides/code/getting_started/config/environments/development.rb @@ -22,10 +22,6 @@ Blog::Application.configure do # Only use best-standards-support built into browsers. config.action_dispatch.best_standards_support = :builtin - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL). - config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Raise an error on page load if there are pending migrations config.active_record.migration_error = :page_load diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb index 58dab2a319..368a735122 100644 --- a/guides/code/getting_started/config/environments/production.rb +++ b/guides/code/getting_started/config/environments/production.rb @@ -72,10 +72,6 @@ Blog::Application.configure do # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL). - # config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Disable automatic flushing of the log to improve performance. # config.autoflush_log = false diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb index 9950568629..0155b613a3 100644 --- a/guides/code/getting_started/config/routes.rb +++ b/guides/code/getting_started/config/routes.rb @@ -2,6 +2,6 @@ Blog::Application.routes.draw do resources :posts do resources :comments end - - root to: "welcome#index" + + root "welcome#index" end diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 9c157ec0b3..463da488f2 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -200,6 +200,8 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ * Remove IdentityMap. +* Remove automatic execution of EXPLAIN queries. The option `active_record.auto_explain_threshold_in_seconds` is no longer used and should be removed. + * Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class. * Added `create_join_table` migration helper to create HABTM join tables. diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 513ae1272f..8720aae169 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -116,7 +116,7 @@ Setting this up is painfully simple. First off, we need to create a simple `User` scaffold: ```bash -$ rails generate scaffold user name:string email:string login:string +$ rails generate scaffold user name email login $ rake db:migrate ``` @@ -447,7 +447,7 @@ end Action Mailer Callbacks --------------------------- -Action Mailer allows for you to specify a `before_action`, `after_action` and 'around_action'. +Action Mailer allows for you to specify a `before_action`, `after_action` and `around_action`. * Filters can be specified with a block or a symbol to a method in the mailer class similar to controllers. @@ -507,7 +507,6 @@ The following configuration options are best made in one of the environment file | Configuration | Description | |---------------|-------------| -|`template_root`|Determines the base from which template references will be made.| |`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.| |`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>| |`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>| diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 516457bcd3..bb42fab101 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -342,19 +342,17 @@ By using the `after_commit` callback we can account for this case. ```ruby class PictureFile < ActiveRecord::Base - attr_accessor :delete_file + after_commit :delete_picture_file_from_disk, :on => [:destroy] - after_destroy do |picture_file| - picture_file.delete_file = picture_file.filepath - end - - after_commit do |picture_file| - if picture_file.delete_file && File.exist?(picture_file.delete_file) - File.delete(picture_file.delete_file) - picture_file.delete_file = nil + def delete_picture_file_from_disk + if File.exist?(filepath) + File.delete(filepath) end end end ``` +NOTE: the `:on` option specifies when a callback will be fired. If you +don't supply the `:on` option the callback will fire for every action. + The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 62d6294ae5..5d82541da9 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -299,7 +299,7 @@ Client.first(2) The SQL equivalent of the above is: ```sql -SELECT * FROM clients LIMIT 2 +SELECT * FROM clients ORDER BY id ASC LIMIT 2 ``` #### last @@ -315,7 +315,7 @@ Client.last(2) The SQL equivalent of the above is: ```sql -SELECT * FROM clients ORDER By id DESC LIMIT 2 +SELECT * FROM clients ORDER BY id DESC LIMIT 2 ``` ### Retrieving Multiple Objects in Batches @@ -1609,45 +1609,6 @@ EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1) under MySQL. -### Automatic EXPLAIN - -Active Record is able to run EXPLAIN automatically on slow queries and log its -output. This feature is controlled by the configuration parameter - -```ruby -config.active_record.auto_explain_threshold_in_seconds -``` - -If set to a number, any query exceeding those many seconds will have its EXPLAIN -automatically triggered and logged. In the case of relations, the threshold is -compared to the total time needed to fetch records. So, a relation is seen as a -unit of work, no matter whether the implementation of eager loading involves -several queries under the hood. - -A threshold of `nil` disables automatic EXPLAINs. - -The default threshold in development mode is 0.5 seconds, and `nil` in test and -production modes. - -INFO. Automatic EXPLAIN gets disabled if Active Record has no logger, regardless -of the value of the threshold. - -#### Disabling Automatic EXPLAIN - -Automatic EXPLAIN can be selectively silenced with `ActiveRecord::Base.silence_auto_explain`: - -```ruby -ActiveRecord::Base.silence_auto_explain do - # no automatic EXPLAIN is triggered here -end -``` - -That may be useful for queries you know are slow but fine, like a heavyweight -report of an admin interface. - -As its name suggests, `silence_auto_explain` only silences automatic EXPLAINs. -Explicit calls to `ActiveRecord::Relation#explain` run. - ### Interpreting EXPLAIN Interpretation of the output of EXPLAIN is beyond the scope of this guide. The diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index f02b377832..517db0d222 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -3320,7 +3320,25 @@ date.end_of_hour # => Mon Jun 07 19:59:59 +0200 2010 `beginning_of_hour` is aliased to `at_beginning_of_hour`. -INFO: `beginning_of_hour` and `end_of_hour` are implemented for `Time` and `DateTime` but **not** `Date` as it does not make sense to request the beginning or end of an hour on a `Date` instance. +##### `beginning_of_minute`, `end_of_minute` + +The method `beginning_of_minute` returns a timestamp at the beginning of the minute (hh:mm:00): + +```ruby +date = DateTime.new(2010, 6, 7, 19, 55, 25) +date.beginning_of_minute # => Mon Jun 07 19:55:00 +0200 2010 +``` + +The method `end_of_minute` returns a timestamp at the end of the minute (hh:mm:59): + +```ruby +date = DateTime.new(2010, 6, 7, 19, 55, 25) +date.end_of_minute # => Mon Jun 07 19:55:59 +0200 2010 +``` + +`beginning_of_minute` is aliased to `at_beginning_of_minute`. + +INFO: `beginning_of_hour`, `end_of_hour`, `beginning_of_minute` and `end_of_minute` are implemented for `Time` and `DateTime` but **not** `Date` as it does not make sense to request the beginning or end of an hour or minute on a `Date` instance. ##### `ago`, `since` diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index a270ec7a7e..abab3dd983 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -343,8 +343,3 @@ class ProductsController < ApplicationController end end ``` - -Further reading ---------------- - -* [Scaling Rails Screencasts](http://railslab.newrelic.com/scaling-rails) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index be46e15078..dbbeec7126 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -268,8 +268,6 @@ config.middleware.delete "Rack::MethodOverride" * `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default. -* `config.active_record.auto_explain_threshold_in_seconds` configures the threshold for automatic EXPLAINs (`nil` disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode. - * +config.active_record.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+. The MySQL adapter adds one additional configuration option: diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 339008ab9e..bfd1a7c61b 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -568,7 +568,8 @@ def show @book = Book.find_by_id(params[:id]) if @book.nil? @books = Book.all - render "index", alert: "Your book was not found!" + flash[:alert] = "Your book was not found" + render "index" end end ``` diff --git a/guides/source/routing.md b/guides/source/routing.md index 4614169653..d7a4a237ed 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -155,7 +155,7 @@ creates six different routes in your application, all mapping to the `Geocoders` | PATCH/PUT | /geocoder | update | update the one and only geocoder resource | | DELETE | /geocoder | destroy | delete the geocoder resource | -NOTE: Because you might want to use the same controller for a singular route (`/account`) and a plural route (`/accounts/45`), singular resources map to plural controllers. +NOTE: Because you might want to use the same controller for a singular route (`/account`) and a plural route (`/accounts/45`), singular resources map to plural controllers. So that, for example, `resource :photo` and `resources :photos` creates both singular and plural routes that map to the same controller (`PhotosController`). A singular resourceful route generates these helpers: @@ -797,6 +797,16 @@ You should put the `root` route at the top of the file, because it is the most p NOTE: The `root` route only routes `GET` requests to the action. +You can also use root inside namespaces and scopes as well. For example: + +```ruby +namespace :admin do + root to: "admin#index" +end + +root to: "home#index" +``` + ### Unicode character routes You can specify unicode character routes directly. For example: diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 18baed55b5..52f5232efc 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -45,6 +45,12 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep * Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`. +* Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) to a smoothly upgrade path. + +### Active Resource + +Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile. + ### Active Model * Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. Now when confirmation validations fail the error will be attached to `:#{attribute}_confirmation` instead of `attribute`. @@ -108,14 +114,14 @@ 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. -The following changes are meant for upgrading your application to Rails 3.2.2, the latest 3.2.x version of Rails. +The following changes are meant for upgrading your application to Rails 3.2.12, the latest 3.2.x version of Rails. ### Gemfile Make the following changes to your `Gemfile`. ```ruby -gem 'rails', '= 3.2.2' +gem 'rails', '= 3.2.12' group :assets do gem 'sass-rails', '~> 3.2.3' @@ -155,14 +161,14 @@ Upgrading from Rails 3.0 to Rails 3.1 If your application is currently on any version of Rails older than 3.0.x, you should upgrade to Rails 3.0 before attempting an update to Rails 3.1. -The following changes are meant for upgrading your application to Rails 3.1.3, the latest 3.1.x version of Rails. +The following changes are meant for upgrading your application to Rails 3.1.11, the latest 3.1.x version of Rails. ### Gemfile Make the following changes to your `Gemfile`. ```ruby -gem 'rails', '= 3.1.3' +gem 'rails', '= 3.1.11' gem 'mysql2' # Needed for the new asset pipeline diff --git a/install.rb b/install.rb index 3967624a3f..77c3e6048a 100644 --- a/install.rb +++ b/install.rb @@ -7,10 +7,10 @@ end %w( activesupport activemodel activerecord actionpack actionmailer railties ).each do |framework| puts "Installing #{framework}..." - `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --local --no-ri --no-rdoc && rm #{framework}-#{version}.gem` + `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem` end -puts "Installing Rails..." +puts "Installing rails..." `gem build rails.gemspec` -`gem install rails-#{version}.gem --local --no-ri --no-rdoc ` +`gem install rails-#{version}.gem --no-ri --no-rdoc ` `rm rails-#{version}.gem` diff --git a/rails.gemspec b/rails.gemspec index 34957c9455..8a5d042361 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -26,6 +26,6 @@ Gem::Specification.new do |s| s.add_dependency 'actionmailer', version s.add_dependency 'railties', version - s.add_dependency 'bundler', '>= 1.2.2', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 2.0.0.rc1' + s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' + s.add_dependency 'sprockets-rails', '~> 2.0.0.rc3' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 1d7513a84f..f3c07470af 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,13 +1,22 @@ ## Rails 4.0.0 (unreleased) ## -* fix rake db:* tasks to work with DATABASE_URL and without config/database.yml - *Terence Lee* -* The configuration option `config.assets.precompile` is no longer - in `config/environments/production.rb` but in - `config/application.rb`. +## Rails 4.0.0.beta1 (February 25, 2013) ## - *Yves Senn* +* Improve `rake stats` for JavaScript and CoffeeScript: ignore block comments + and calculates number of functions. + + *Hendy Tanata* + +* Ability to use a custom builder by passing `--builder` (or `-b`) has been removed. Consider + using application template instead. See this guide for more detail: + http://guides.rubyonrails.org/rails_application_templates.html + + *Prem Sichanugrist* + +* fix rake db:* tasks to work with DATABASE_URL and without config/database.yml + + *Terence Lee* * Add notice message for destroy action in scaffold generator. diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb index 8937e10db3..44f4d3dabc 100644 --- a/railties/lib/rails/app_rails_loader.rb +++ b/railties/lib/rails/app_rails_loader.rb @@ -3,12 +3,16 @@ require 'pathname' module Rails module AppRailsLoader RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] - EXECUTABLE = 'bin/rails' + EXECUTABLES = ['bin/rails', 'script/rails'] def self.exec_app_rails - cwd = Dir.pwd - return unless in_rails_application_or_engine? || in_rails_application_or_engine_subdirectory? - exec RUBY, EXECUTABLE, *ARGV if in_rails_application_or_engine? + cwd = Dir.pwd + + exe = find_executable + exe ||= find_executable_in_parent_path + return unless exe + + exec RUBY, exe, *ARGV if find_executable Dir.chdir("..") do # Recurse in a chdir block: if the search fails we want to be sure # the application is generated in the original working directory. @@ -18,12 +22,16 @@ module Rails # could not chdir, no problem just return end - def self.in_rails_application_or_engine? - File.exists?(EXECUTABLE) && File.read(EXECUTABLE) =~ /(APP|ENGINE)_PATH/ + def self.find_executable + EXECUTABLES.find do |exe| + File.exists?(exe) && File.read(exe) =~ /(APP|ENGINE)_PATH/ + end end - def self.in_rails_application_or_engine_subdirectory?(path = Pathname.new(Dir.pwd)) - File.exists?(File.join(path, EXECUTABLE)) || !path.root? && in_rails_application_or_engine_subdirectory?(path.parent) + def self.find_executable_in_parent_path(path = Pathname.new(Dir.pwd)) + EXECUTABLES.find do |exe| + File.exists?(File.join(path, exe)) || !path.root? && find_executable_in_parent_path(path.parent) + end end end end diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index b717b026de..e5341ac436 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -3,9 +3,6 @@ require 'rails/app_rails_loader' # If we are inside a Rails application this method performs an exec and thus # the rest of this script is not run. -# -# TODO: when we hit this, advise adding ./bin to $PATH instead. Then the -# app's `rails` executable is run immediately. Rails::AppRailsLoader.exec_app_rails require 'rails/ruby_version_check' diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 039360fcf6..0ae6d2a455 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -1,3 +1,5 @@ +require 'rails/code_statistics_calculator' + class CodeStatistics #:nodoc: TEST_TYPES = ['Controller tests', @@ -33,64 +35,38 @@ class CodeStatistics #:nodoc: end def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee)$/) - stats = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 } + stats = CodeStatisticsCalculator.new Dir.foreach(directory) do |file_name| - if File.directory?(directory + "/" + file_name) and (/^\./ !~ file_name) - newstats = calculate_directory_statistics(directory + "/" + file_name, pattern) - stats.each { |k, v| stats[k] += newstats[k] } + path = "#{directory}/#{file_name}" + + if File.directory?(path) && (/^\./ !~ file_name) + stats.add(calculate_directory_statistics(path, pattern)) end next unless file_name =~ pattern - comment_started = false - - case file_name - when /.*\.js$/ - comment_pattern = /^\s*\/\// - else - comment_pattern = /^\s*#/ - end - - File.open(directory + "/" + file_name) do |f| - while line = f.gets - stats["lines"] += 1 - if(comment_started) - if line =~ /^=end/ - comment_started = false - end - next - else - if line =~ /^=begin/ - comment_started = true - next - end - end - stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/ - stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/ - stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ comment_pattern - end - end + stats.add_by_file_path(path) end stats end def calculate_total - total = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 } - @statistics.each_value { |pair| pair.each { |k, v| total[k] += v } } - total + @statistics.each_with_object(CodeStatisticsCalculator.new) do |pair, total| + total.add(pair.last) + end end def calculate_code code_loc = 0 - @statistics.each { |k, v| code_loc += v['codelines'] unless TEST_TYPES.include? k } + @statistics.each { |k, v| code_loc += v.code_lines unless TEST_TYPES.include? k } code_loc end def calculate_tests test_loc = 0 - @statistics.each { |k, v| test_loc += v['codelines'] if TEST_TYPES.include? k } + @statistics.each { |k, v| test_loc += v.code_lines if TEST_TYPES.include? k } test_loc end @@ -105,15 +81,15 @@ class CodeStatistics #:nodoc: end def print_line(name, statistics) - m_over_c = (statistics["methods"] / statistics["classes"]) rescue m_over_c = 0 - loc_over_m = (statistics["codelines"] / statistics["methods"]) - 2 rescue loc_over_m = 0 - - puts "| #{name.ljust(20)} " + - "| #{statistics["lines"].to_s.rjust(5)} " + - "| #{statistics["codelines"].to_s.rjust(5)} " + - "| #{statistics["classes"].to_s.rjust(7)} " + - "| #{statistics["methods"].to_s.rjust(7)} " + - "| #{m_over_c.to_s.rjust(3)} " + + m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0 + loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0 + + puts "| #{name.ljust(20)} " \ + "| #{statistics.lines.to_s.rjust(5)} " \ + "| #{statistics.code_lines.to_s.rjust(5)} " \ + "| #{statistics.classes.to_s.rjust(7)} " \ + "| #{statistics.methods.to_s.rjust(7)} " \ + "| #{m_over_c.to_s.rjust(3)} " \ "| #{loc_over_m.to_s.rjust(5)} |" end diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb new file mode 100644 index 0000000000..60e4aef9b7 --- /dev/null +++ b/railties/lib/rails/code_statistics_calculator.rb @@ -0,0 +1,79 @@ +class CodeStatisticsCalculator #:nodoc: + attr_reader :lines, :code_lines, :classes, :methods + + PATTERNS = { + rb: { + line_comment: /^\s*#/, + begin_block_comment: /^=begin/, + end_block_comment: /^=end/, + class: /^\s*class\s+[_A-Z]/, + method: /^\s*def\s+[_a-z]/, + }, + js: { + line_comment: %r{^\s*//}, + begin_block_comment: %r{^\s*/\*}, + end_block_comment: %r{\*/}, + method: /function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/, + }, + coffee: { + line_comment: /^\s*#/, + begin_block_comment: /^\s*###/, + end_block_comment: /^\s*###/, + class: /^\s*class\s+[_A-Z]/, + method: /[-=]>/, + } + } + + def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0) + @lines = lines + @code_lines = code_lines + @classes = classes + @methods = methods + end + + def add(code_statistics_calculator) + @lines += code_statistics_calculator.lines + @code_lines += code_statistics_calculator.code_lines + @classes += code_statistics_calculator.classes + @methods += code_statistics_calculator.methods + end + + def add_by_file_path(file_path) + File.open(file_path) do |f| + self.add_by_io(f, file_type(file_path)) + end + end + + def add_by_io(io, file_type) + patterns = PATTERNS[file_type] || {} + + comment_started = false + + while line = io.gets + @lines += 1 + + if comment_started + if patterns[:end_block_comment] && line =~ patterns[:end_block_comment] + comment_started = false + end + next + else + if patterns[:begin_block_comment] && line =~ patterns[:begin_block_comment] + comment_started = true + next + end + end + + @classes += 1 if patterns[:class] && line =~ patterns[:class] + @methods += 1 if patterns[:method] && line =~ patterns[:method] + if line !~ /^\s*$/ && (patterns[:line_comment].nil? || line !~ patterns[:line_comment]) + @code_lines += 1 + end + end + end + + private + def file_type(file_path) + File.extname(file_path).sub(/\A\./, '').downcase.to_sym + end +end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 46a6485c44..579af8c6a5 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -1,4 +1,5 @@ require 'rails/railtie' +require 'rails/engine/railties' require 'active_support/core_ext/module/delegation' require 'pathname' require 'rbconfig' @@ -467,7 +468,7 @@ module Rails end def railties - @railties ||= self.class::Railties.new + @railties ||= Railties.new end # Returns a module with all the helpers defined for the engine. diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index cb3aca5811..28593c5907 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -215,7 +215,7 @@ module Rails # Make an entry in Rails routing file config/routes.rb # - # route "root :to => 'welcome#index'" + # route "root 'welcome#index'" def route(routing_code) log :route, routing_code sentinel = /\.routes\.draw do\s*$/ diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 99d80b3245..4e703151ba 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -19,9 +19,6 @@ module Rails argument :app_path, type: :string def self.add_shared_options_for(name) - class_option :builder, type: :string, aliases: '-b', - desc: "Path to some #{name} builder (can be a filesystem path or URL)" - class_option :template, type: :string, aliases: '-m', desc: "Path to some #{name} template (can be a filesystem path or URL)" @@ -81,17 +78,6 @@ module Rails def builder @builder ||= begin - if path = options[:builder] - if URI(path).is_a?(URI::HTTP) - contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read } - else - contents = open(File.expand_path(path, @original_wd)) {|io| io.read } - end - - prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1) - instance_eval(&prok) - end - builder_class = get_builder_class builder_class.send(:include, ActionMethods) builder_class.new(self) @@ -210,9 +196,8 @@ module Rails # Gems used only for assets and not required # in production environments by default. group :assets do - gem 'sprockets-rails', '~> 2.0.0.rc1' - gem 'sass-rails', '~> 4.0.0.beta' - gem 'coffee-rails', '~> 4.0.0.beta' + gem 'sass-rails', '~> 4.0.0.beta1' + gem 'coffee-rails', '~> 4.0.0.beta1' # See https://github.com/sstephenson/execjs#readme for more supported runtimes #{javascript_runtime_gemfile_entry} diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 1275bce7e6..daf399a538 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -31,10 +31,6 @@ module <%= app_const_base %> # Disable the asset pipeline. config.assets.enabled = false -<% else %> - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) <% end -%> end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index c4cc1162a4..d0e62d09cc 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -20,10 +20,6 @@ config.active_support.deprecation = :log <%- unless options.skip_active_record? -%> - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL). - config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Raise an error on page load if there are pending migrations config.active_record.migration_error = :page_load <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 1f0111368b..5669fe6d64 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -59,6 +59,12 @@ # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = "http://assets.example.com" + <%- unless options.skip_sprockets? -%> + # Precompile additional assets. + # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. + # config.assets.precompile += %w( search.js ) + <%- end -%> + # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false @@ -70,12 +76,6 @@ # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify - <%- unless options.skip_active_record? -%> - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL). - # config.active_record.auto_explain_threshold_in_seconds = 0.5 - <%- end -%> - # Disable automatic flushing of the log to improve performance. # config.autoflush_log = false 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 22a6aeb5fe..f877fa1f8a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -3,7 +3,7 @@ # See how all your routes lay out with "rake routes". # You can have the root of your site routed with "root" - # root to: 'welcome#index' + # root 'welcome#index' # Example of regular route: # get 'products/:id' => 'catalog#view' 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 af00748037..5fe01d0961 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 @@ -94,6 +94,11 @@ task default: :test end end + def test_dummy_assets + template "rails/javascripts.js", "#{dummy_path}/app/assets/javascripts/application.js", force: true + template "rails/stylesheets.css", "#{dummy_path}/app/assets/stylesheets/application.css", force: true + end + def test_dummy_clean inside dummy_path do remove_file ".gitignore" @@ -112,7 +117,7 @@ task default: :test def stylesheets if mountable? - copy_file "#{app_templates_dir}/app/assets/stylesheets/application.css", + copy_file "rails/stylesheets.css", "app/assets/stylesheets/#{name}/application.css" elsif full? empty_directory_with_keep_file "app/assets/stylesheets/#{name}" @@ -123,8 +128,8 @@ task default: :test return if options.skip_javascript? if mountable? - template "#{app_templates_dir}/app/assets/javascripts/application.js.tt", - "app/assets/javascripts/#{name}/application.js" + template "rails/javascripts.js", + "app/assets/javascripts/#{name}/application.js" elsif full? empty_directory_with_keep_file "app/assets/javascripts/#{name}" end @@ -263,6 +268,7 @@ task default: :test build(:generate_test_dummy) store_application_definition! build(:test_dummy_config) + build(:test_dummy_assets) build(:test_dummy_clean) # ensure that bin/rails has proper dummy_path build(:bin, true) diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec index e956d13d8a..f7c12e67dd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec @@ -19,9 +19,6 @@ Gem::Specification.new do |s| <% end -%> <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>" -<% if engine? && !options[:skip_javascript] -%> - # s.add_dependency "<%= "#{options[:javascript]}-rails" %>" -<% end -%> <% unless options[:skip_active_record] -%> s.add_development_dependency "<%= gem_for_database %>" diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index a8b5bfaf3f..3f2b78f2fd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -2,9 +2,6 @@ source "https://rubygems.org" <% if options[:skip_gemspec] -%> <%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>" -<% if engine? && !options[:skip_javascript] -%> -# gem "<%= "#{options[:javascript]}-rails" %>" -<% end -%> <% else -%> # Declare your gem's dependencies in <%= name %>.gemspec. # Bundler will treat runtime dependencies like base dependencies, and @@ -12,11 +9,6 @@ source "https://rubygems.org" gemspec <% end -%> -<% unless options[:javascript] == 'jquery' -%> -# jquery-rails is used by the dummy application -gem "jquery-rails" - -<% end -%> <% if options[:skip_gemspec] -%> group :development do gem "<%= gem_for_database %>" diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb index c78bfb7f63..ef360470a3 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb @@ -1,9 +1,5 @@ -gemfile = File.expand_path('../../../../Gemfile', __FILE__) +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) -if File.exist?(gemfile) - ENV['BUNDLE_GEMFILE'] = gemfile - require 'bundler' - Bundler.setup -end - -$:.unshift File.expand_path('../../../../lib', __FILE__)
\ No newline at end of file +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) +$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js new file mode 100644 index 0000000000..084d5d1c49 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. +// +// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +// GO AFTER THE REQUIRES BELOW. +// +//= require_tree . diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css new file mode 100644 index 0000000000..3192ec897b --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css @@ -0,0 +1,13 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the top of the + * compiled file, but it's generally better to create a new file per style scope. + * + *= require_self + *= require_tree . + */ 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 72281a2fef..73e89086a5 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -56,7 +56,7 @@ class <%= controller_class_name %>Controller < ApplicationController @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> end - # Never trust parameters from the scary internet, only allow the white list through. + # Only allow a trusted parameter "white list" through. def <%= "#{singular_table_name}_params" %> <%- if attributes_names.empty? -%> params[<%= ":#{singular_table_name}" %>] diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index ea6c074bdc..0057b0f887 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -102,7 +102,7 @@ namespace :doc do # desc "Generate Rails Guides" task :guides do # FIXME: Reaching outside lib directory is a bad idea - require File.expand_path('../../../../guides/rails_guides', __FILE__) + require File.expand_path('../../../../../guides/rails_guides', __FILE__) RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate end end diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index ec878f9dcf..87fc7690ac 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -3,7 +3,7 @@ module Rails MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_rails_loader_test.rb index 87e0ad7bd7..63ed9eaef0 100644 --- a/railties/test/app_rails_loader_test.rb +++ b/railties/test/app_rails_loader_test.rb @@ -2,40 +2,47 @@ require 'abstract_unit' require 'rails/app_rails_loader' class AppRailsLoaderTest < ActiveSupport::TestCase - test "is in a rails application if bin/rails exists and contains APP_PATH" do - File.stubs(:exists?).returns(true) - File.stubs(:read).with('bin/rails').returns('APP_PATH') - assert Rails::AppRailsLoader.in_rails_application_or_engine? - end - test "is not in a rails application if bin/rails exists but doesn't contain APP_PATH" do - File.stubs(:exists?).returns(true) - File.stubs(:read).with('bin/rails').returns('railties bin/rails') - assert !Rails::AppRailsLoader.in_rails_application_or_engine? + setup do + File.stubs(:exists?).returns(false) end - test "is in a rails application if parent directory has bin/rails containing APP_PATH" do - File.stubs(:exists?).with("/foo/bar/bin/rails").returns(false) - File.stubs(:exists?).with("/foo/bin/rails").returns(true) - File.stubs(:read).with('/foo/bin/rails').returns('APP_PATH') - assert Rails::AppRailsLoader.in_rails_application_or_engine_subdirectory?(Pathname.new("/foo/bar")) - end + ['bin/rails', 'script/rails'].each do |exe| + test "is in a rails application if #{exe} exists and contains APP_PATH" do + File.stubs(:exists?).with(exe).returns(true) + File.stubs(:read).with(exe).returns('APP_PATH') + assert Rails::AppRailsLoader.find_executable + end - test "is not in a rails application if at the root directory and doesn't have bin/rails" do - Pathname.any_instance.stubs(:root?).returns true - assert !Rails::AppRailsLoader.in_rails_application_or_engine? - end + test "is not in a rails application if #{exe} exists but doesn't contain APP_PATH" do + File.stubs(:exists?).with(exe).returns(true) + File.stubs(:read).with(exe).returns("railties #{exe}") + assert !Rails::AppRailsLoader.find_executable + end - test "is in a rails engine if parent directory has bin/rails containing ENGINE_PATH" do - File.stubs(:exists?).with("/foo/bar/bin/rails").returns(false) - File.stubs(:exists?).with("/foo/bin/rails").returns(true) - File.stubs(:read).with('/foo/bin/rails').returns('ENGINE_PATH') - assert Rails::AppRailsLoader.in_rails_application_or_engine_subdirectory?(Pathname.new("/foo/bar")) - end + test "is in a rails application if parent directory has #{exe} containing APP_PATH" do + File.stubs(:exists?).with("/foo/bar/#{exe}").returns(false) + File.stubs(:exists?).with("/foo/#{exe}").returns(true) + File.stubs(:read).with("/foo/#{exe}").returns('APP_PATH') + assert Rails::AppRailsLoader.find_executable_in_parent_path(Pathname.new("/foo/bar")) + end + + test "is not in a rails application if at the root directory and doesn't have #{exe}" do + Pathname.any_instance.stubs(:root?).returns true + assert !Rails::AppRailsLoader.find_executable + end + + test "is in a rails engine if parent directory has #{exe} containing ENGINE_PATH" do + File.stubs(:exists?).with("/foo/bar/#{exe}").returns(false) + File.stubs(:exists?).with("/foo/#{exe}").returns(true) + File.stubs(:read).with("/foo/#{exe}").returns('ENGINE_PATH') + assert Rails::AppRailsLoader.find_executable_in_parent_path(Pathname.new("/foo/bar")) + end - test "is in a rails engine if bin/rails exists containing ENGINE_PATH" do - File.stubs(:exists?).returns(true) - File.stubs(:read).with('bin/rails').returns('ENGINE_PATH') - assert Rails::AppRailsLoader.in_rails_application_or_engine? + test "is in a rails engine if #{exe} exists containing ENGINE_PATH" do + File.stubs(:exists?).with(exe).returns(true) + File.stubs(:read).with(exe).returns('ENGINE_PATH') + assert Rails::AppRailsLoader.find_executable + end end end diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb new file mode 100644 index 0000000000..b3eabf5024 --- /dev/null +++ b/railties/test/code_statistics_calculator_test.rb @@ -0,0 +1,288 @@ +require 'abstract_unit' +require 'rails/code_statistics_calculator' + +class CodeStatisticsCalculatorTest < ActiveSupport::TestCase + def setup + @code_statistics_calculator = CodeStatisticsCalculator.new + end + + test 'add statistics to another using #add' do + code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4) + @code_statistics_calculator.add(code_statistics_calculator_1) + + assert_equal 1, @code_statistics_calculator.lines + assert_equal 2, @code_statistics_calculator.code_lines + assert_equal 3, @code_statistics_calculator.classes + assert_equal 4, @code_statistics_calculator.methods + + code_statistics_calculator_2 = CodeStatisticsCalculator.new(2, 3, 4, 5) + @code_statistics_calculator.add(code_statistics_calculator_2) + + assert_equal 3, @code_statistics_calculator.lines + assert_equal 5, @code_statistics_calculator.code_lines + assert_equal 7, @code_statistics_calculator.classes + assert_equal 9, @code_statistics_calculator.methods + end + + test 'accumulate statistics using #add_by_io' do + code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4) + @code_statistics_calculator.add(code_statistics_calculator_1) + + code = <<-'CODE' + def foo + puts 'foo' + end + + def bar; end + class A; end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 7, @code_statistics_calculator.lines + assert_equal 7, @code_statistics_calculator.code_lines + assert_equal 4, @code_statistics_calculator.classes + assert_equal 6, @code_statistics_calculator.methods + end + + test 'calculate statistics using #add_by_file_path' do + tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp')) + FileUtils.mkdir_p(tmp_path) + + code = <<-'CODE' + def foo + puts 'foo' + # bar + end + CODE + + file_path = "#{tmp_path}/stats.rb" + File.open(file_path, 'w') { |f| f.write(code) } + + @code_statistics_calculator.add_by_file_path(file_path) + + assert_equal 4, @code_statistics_calculator.lines + assert_equal 3, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 1, @code_statistics_calculator.methods + + FileUtils.rm_rf(tmp_path) + end + + test 'calculate number of Ruby methods' do + code = <<-'CODE' + def foo + puts 'foo' + end + + def bar; end + + class Foo + def bar(abc) + end + end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 3, @code_statistics_calculator.methods + end + + test 'calculate Ruby LOCs' do + code = <<-'CODE' + def foo + puts 'foo' + end + + # def bar; end + + class A < B + end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 8, @code_statistics_calculator.lines + assert_equal 5, @code_statistics_calculator.code_lines + end + + test 'calculate number of Ruby classes' do + code = <<-'CODE' + class Foo < Bar + def foo + puts 'foo' + end + end + + class Z; end + + # class A + # end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 2, @code_statistics_calculator.classes + end + + test 'skip Ruby comments' do + code = <<-'CODE' +=begin + class Foo + def foo + puts 'foo' + end + end +=end + + # class A + # end + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rb) + + assert_equal 10, @code_statistics_calculator.lines + assert_equal 0, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 0, @code_statistics_calculator.methods + end + + test 'calculate number of JS methods' do + code = <<-'CODE' + function foo(x, y, z) { + doX(); + } + + $(function () { + bar(); + }) + + var baz = function ( x ) { + } + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :js) + + assert_equal 3, @code_statistics_calculator.methods + end + + test 'calculate JS LOCs' do + code = <<-'CODE' + function foo() + alert('foo'); + end + + // var b = 2; + + var a = 1; + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :js) + + assert_equal 7, @code_statistics_calculator.lines + assert_equal 4, @code_statistics_calculator.code_lines + end + + test 'skip JS comments' do + code = <<-'CODE' + /* + * var f = function () { + 1 / 2; + } + */ + + // call(); + // + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :js) + + assert_equal 8, @code_statistics_calculator.lines + assert_equal 0, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 0, @code_statistics_calculator.methods + end + + test 'calculate number of CoffeeScript methods' do + code = <<-'CODE' + square = (x) -> x * x + + math = + cube: (x) -> x * square x + + fill = (container, liquid = "coffee") -> + "Filling the #{container} with #{liquid}..." + + $('.shopping_cart').bind 'click', (event) => + @customer.purchase @cart + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee) + + assert_equal 4, @code_statistics_calculator.methods + end + + test 'calculate CoffeeScript LOCs' do + code = <<-'CODE' + # Assignment: + number = 42 + opposite = true + + ### + CoffeeScript Compiler v1.4.0 + Released under the MIT License + ### + + # Conditions: + number = -42 if opposite + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee) + + assert_equal 11, @code_statistics_calculator.lines + assert_equal 3, @code_statistics_calculator.code_lines + end + + test 'calculate number of CoffeeScript classes' do + code = <<-'CODE' + class Animal + constructor: (@name) -> + + move: (meters) -> + alert @name + " moved #{meters}m." + + class Snake extends Animal + move: -> + alert "Slithering..." + super 5 + + # class Horse + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee) + + assert_equal 2, @code_statistics_calculator.classes + end + + test 'skip CoffeeScript comments' do + code = <<-'CODE' +### +class Animal + constructor: (@name) -> + + move: (meters) -> + alert @name + " moved #{meters}m." + ### + + # class Horse + alert 'hello' + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :coffee) + + assert_equal 10, @code_statistics_calculator.lines + assert_equal 1, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 0, @code_statistics_calculator.methods + end +end diff --git a/railties/test/fixtures/lib/app_builders/empty_builder.rb b/railties/test/fixtures/lib/app_builders/empty_builder.rb deleted file mode 100644 index babd9c2461..0000000000 --- a/railties/test/fixtures/lib/app_builders/empty_builder.rb +++ /dev/null @@ -1,2 +0,0 @@ -class AppBuilder -end
\ No newline at end of file diff --git a/railties/test/fixtures/lib/app_builders/simple_builder.rb b/railties/test/fixtures/lib/app_builders/simple_builder.rb deleted file mode 100644 index 993d3a2aa2..0000000000 --- a/railties/test/fixtures/lib/app_builders/simple_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AppBuilder - def gitignore - create_file ".gitignore", <<-R.strip -foobar - R - end -end diff --git a/railties/test/fixtures/lib/app_builders/tweak_builder.rb b/railties/test/fixtures/lib/app_builders/tweak_builder.rb deleted file mode 100644 index cb50be01cb..0000000000 --- a/railties/test/fixtures/lib/app_builders/tweak_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AppBuilder < Rails::AppBuilder - def gitignore - create_file ".gitignore", <<-R.strip -foobar - R - end -end diff --git a/railties/test/fixtures/lib/plugin_builders/empty_builder.rb b/railties/test/fixtures/lib/plugin_builders/empty_builder.rb deleted file mode 100644 index 5c5607621c..0000000000 --- a/railties/test/fixtures/lib/plugin_builders/empty_builder.rb +++ /dev/null @@ -1,2 +0,0 @@ -class PluginBuilder -end diff --git a/railties/test/fixtures/lib/plugin_builders/simple_builder.rb b/railties/test/fixtures/lib/plugin_builders/simple_builder.rb deleted file mode 100644 index 08f6c5535d..0000000000 --- a/railties/test/fixtures/lib/plugin_builders/simple_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class PluginBuilder - def gitignore - create_file ".gitignore", <<-R.strip -foobar - R - end -end diff --git a/railties/test/fixtures/lib/plugin_builders/spec_builder.rb b/railties/test/fixtures/lib/plugin_builders/spec_builder.rb deleted file mode 100644 index 2721429599..0000000000 --- a/railties/test/fixtures/lib/plugin_builders/spec_builder.rb +++ /dev/null @@ -1,19 +0,0 @@ -class PluginBuilder < Rails::PluginBuilder - def test - create_file "spec/spec_helper.rb" - append_file "Rakefile", <<-EOF -# spec tasks in rakefile - -task default: :spec - EOF - end - - def generate_test_dummy - dummy_path("spec/dummy") - super - end - - def skip_test_unit? - true - end -end diff --git a/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb b/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb deleted file mode 100644 index 1e801409a4..0000000000 --- a/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class PluginBuilder < Rails::PluginBuilder - def gitignore - create_file ".gitignore", <<-R.strip -foobar - R - end -end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 83f43d1025..0697035871 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -342,15 +342,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generated_environments_file_for_auto_explain - run_generator [destination_root, "--skip-active-record"] - %w(development production).each do |env| - assert_file "config/environments/#{env}.rb" do |file| - assert_no_match %r(auto_explain_threshold_in_seconds), file - end - end - end - def test_pretend_option output = run_generator [File.join(destination_root, "myapp"), "--pretend"] assert_no_match(/run bundle install/, output) @@ -366,28 +357,3 @@ protected assert_file "Gemfile", /^gem\s+["']#{gem}["']$/ end end - -class CustomAppGeneratorTest < Rails::Generators::TestCase - include GeneratorsTestHelper - tests Rails::Generators::AppGenerator - - arguments [destination_root] - include SharedCustomGeneratorTests - -protected - def default_files - ::DEFAULT_APP_FILES - end - - def builders_dir - "app_builders" - end - - def builder_class - :AppBuilder - end - - def action(*args, &block) - silence(:stdout) { generator.send(*args, &block) } - end -end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 4bf5a2f08b..34441ef679 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -63,13 +63,24 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_directory "test/integration/" assert_no_file "test" - assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile"))) + assert_file "Rakefile" do |contents| + assert_no_match(/APP_RAKEFILE/, contents) + end end def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app'] + assert_file "Rakefile", /APP_RAKEFILE/ + end + + def test_generating_adds_dummy_app_without_javascript_and_assets_deps + run_generator [destination_root] + + assert_file "test/dummy/app/assets/stylesheets/application.css" - assert_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile"))) + assert_file "test/dummy/app/assets/javascripts/application.js" do |contents| + assert_no_match(/jquery/, contents) + end end def test_ensure_that_plugin_options_are_not_passed_to_app_generator @@ -112,7 +123,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_ensure_that_skip_active_record_option_is_passed_to_app_generator run_generator [destination_root, "--skip_active_record"] assert_no_file "test/dummy/config/database.yml" - assert_no_match(/ActiveRecord/, File.read(File.join(destination_root, "test/test_helper.rb"))) + assert_file "test/test_helper.rb" do |contents| + assert_no_match /ActiveRecord/, contents + end end def test_ensure_that_database_option_is_passed_to_app_generator @@ -134,8 +147,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_skipping_javascripts_without_mountable_option run_generator assert_no_file "app/assets/javascripts/bukkits/application.js" - assert_no_file "vendor/assets/javascripts/jquery.js" - assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end def test_javascripts_generation @@ -143,33 +154,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "app/assets/javascripts/bukkits/application.js" end - def test_jquery_is_the_default_javascript_library - run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts/bukkits/application.js" do |contents| - assert_match %r{^//= require jquery}, contents - assert_match %r{^//= require jquery_ujs}, contents - end - assert_file 'bukkits.gemspec' do |contents| - assert_match(/jquery-rails/, contents) - end - end - - def test_other_javascript_libraries - run_generator [destination_root, "--mountable", '-j', 'prototype'] - assert_file "app/assets/javascripts/bukkits/application.js" do |contents| - assert_match %r{^//= require prototype}, contents - assert_match %r{^//= require prototype_ujs}, contents - end - assert_file 'bukkits.gemspec' do |contents| - assert_match(/prototype-rails/, contents) - end - end - def test_skip_javascripts run_generator [destination_root, "--skip-javascript", "--mountable"] assert_no_file "app/assets/javascripts/bukkits/application.js" - assert_no_file "vendor/assets/javascripts/jquery.js" - assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end def test_template_from_dir_pwd @@ -318,16 +305,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_match('gemspec', contents) assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents) assert_match(/group :development do\n gem "sqlite3"\nend/, contents) - assert_match(/# gem "jquery-rails"/, contents) - assert_no_match(/# jquery-rails is used by the dummy application\ngem "jquery-rails"/, contents) - end - end - - def test_skipping_gemspec_in_full_mode_with_javascript_option - run_generator [destination_root, "--skip-gemspec", "--full", "--javascript=prototype"] - assert_file "Gemfile" do |contents| - assert_match(/# gem "prototype-rails"/, contents) - assert_match(/# jquery-rails is used by the dummy application\ngem "jquery-rails"/, contents) end end @@ -371,38 +348,3 @@ protected ::DEFAULT_PLUGIN_FILES end end - -class CustomPluginGeneratorTest < Rails::Generators::TestCase - include GeneratorsTestHelper - tests Rails::Generators::PluginNewGenerator - - destination File.join(Rails.root, "tmp/bukkits") - arguments [destination_root] - include SharedCustomGeneratorTests - - def test_overriding_test_framework - FileUtils.cd(destination_root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/plugin_builders/spec_builder.rb"]) - assert_file 'spec/spec_helper.rb' - assert_file 'spec/dummy' - assert_file 'Rakefile', /task default: :spec/ - assert_file 'Rakefile', /# spec tasks in rakefile/ - end - -protected - def default_files - ::DEFAULT_PLUGIN_FILES - end - - def builder_class - :PluginBuilder - end - - def builders_dir - "plugin_builders" - end - - def action(*args, &block) - silence(:stdout){ generator.send(*args, &block) } - end -end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 2081f4aba8..2724882a23 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -140,61 +140,3 @@ module SharedGeneratorTests assert_no_file('app/mailers/.keep') end end - -module SharedCustomGeneratorTests - def setup - Rails.application = TestApp::Application - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - end - - def teardown - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - Object.class_eval do - remove_const :AppBuilder if const_defined?(:AppBuilder) - remove_const :PluginBuilder if const_defined?(:PluginBuilder) - end - Rails.application = TestApp::Application.instance - end - - def test_builder_option_with_empty_app_builder - FileUtils.cd(destination_root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/empty_builder.rb"]) - default_files.each{ |path| assert_no_file path } - end - - def test_builder_option_with_simple_plugin_builder - FileUtils.cd(destination_root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/simple_builder.rb"]) - (default_files - ['.gitignore']).each{ |path| assert_no_file path } - assert_file ".gitignore", "foobar" - end - - def test_builder_option_with_relative_path - here = File.expand_path(File.dirname(__FILE__)) - FileUtils.cd(here) - run_generator([destination_root, "-b", "../fixtures/lib/#{builders_dir}/simple_builder.rb"]) - FileUtils.cd(destination_root) - (default_files - ['.gitignore']).each{ |path| assert_no_file path } - assert_file ".gitignore", "foobar" - end - - def test_builder_option_with_tweak_plugin_builder - FileUtils.cd(destination_root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/tweak_builder.rb"]) - default_files.each{ |path| assert_file path } - assert_file ".gitignore", "foobar" - end - - def test_builder_option_with_http - url = "https://gist.github.com/josevalim/103208/raw/" - template = "class #{builder_class}; end" - template.instance_eval "def read; self; end" # Make the string respond to read - - generator([destination_root], builder: url).expects(:open).with(url, 'Accept' => 'application/x-thor-template').returns(template) - quietly { generator.invoke_all } - - default_files.each{ |path| assert_no_file(path) } - end -end diff --git a/version.rb b/version.rb index ec878f9dcf..87fc7690ac 100644 --- a/version.rb +++ b/version.rb @@ -3,7 +3,7 @@ module Rails MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end |