diff options
358 files changed, 4700 insertions, 2054 deletions
diff --git a/.gitignore b/.gitignore index 854fdbf450..a3a5304ecd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,7 @@ debug.log .Gemfile /.bundle -/.rbenv-version -/.rvmrc +/.ruby-version /Gemfile.lock /pkg /dist @@ -8,13 +8,10 @@ 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.1.4', github: 'rails/jquery-rails' +gem 'jquery-rails', '~> 2.2.0', github: 'rails/jquery-rails' gem 'turbolinks' gem 'coffee-rails', github: 'rails/coffee-rails' -# TODO: Release thor -gem 'thor', github: 'wycats/thor', branch: 'master' - gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders', branch: 'master' # Needed for compiling the ActionDispatch::Journey parser diff --git a/README.rdoc b/README.rdoc index 91a5f27add..bb9c418e0b 100644 --- a/README.rdoc +++ b/README.rdoc @@ -27,7 +27,7 @@ and render view templates in order to generate the appropriate HTTP response. In Rails, the Controller and View layers are handled together by Action Pack. These two layers are bundled in a single package due to their heavy interdependence. -This is unlike the relationship between Active Record and Action Pack which are +This is unlike the relationship between Active Record and Action Pack, which are independent. Each of these packages can be used independently outside of Rails. You can read more about Action Pack in its {README}[link:/rails/rails/blob/master/actionpack/README.rdoc]. diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc index 9af79f73e2..b065be4922 100644 --- a/RELEASING_RAILS.rdoc +++ b/RELEASING_RAILS.rdoc @@ -42,6 +42,9 @@ addressed, and that can impact your release date. Ruby implementors have high stakes in making sure Rails works. Be kind and give them a heads up that Rails will be released soonish. +This only needs done for major and minor releases, bugfix releases aren't a +big enough deal, and are supposed to be backwards compatible. + Send an email just giving a heads up about the upcoming release to these lists: diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 43bfa536c1..7e57fb39f3 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,6 +1,23 @@ ## Rails 4.0.0 (unreleased) ## -* Eager loading made to use relation's in_clause_length instead of host's one. +* Allow passing interpolations to `#default_i18n_subject`, e.g.: + + # config/locales/en.yml + en: + user_mailer: + welcome: + subject: 'Hello, %{username}' + + # app/mailers/user_mailer.rb + class UserMailer < ActionMailer::Base + def welcome(user) + mail(subject: default_i18n_subject(username: user.name)) + end + end + + *Olek Janiszewski* + +* Eager loading made to use relation's `in_clause_length` instead of host's one. Fix #8474 *Boris Staal* diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 9ba606c045..ff74185e37 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -726,9 +726,10 @@ module ActionMailer # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope. # If it does not find a translation for the +subject+ under the specified scope it will default to a # humanized version of the <tt>action_name</tt>. - def default_i18n_subject #:nodoc: + # If the subject has interpolations, you can pass them through the +interpolations+ parameter. + def default_i18n_subject(interpolations = {}) mailer_scope = self.class.mailer_name.tr('/', '.') - I18n.t(:subject, scope: [mailer_scope, action_name], default: action_name.humanize) + I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize)) end def collect_responses(headers) #:nodoc: @@ -747,7 +748,7 @@ module ActionMailer templates_path = headers.delete(:template_path) || self.class.mailer_name templates_name = headers.delete(:template_name) || action_name - each_template(templates_path, templates_name) do |template| + each_template(Array(templates_path), templates_name) do |template| self.formats = template.formats responses << { @@ -761,9 +762,9 @@ module ActionMailer end def each_template(paths, name, &block) #:nodoc: - templates = lookup_context.find_all(name, Array(paths)) + templates = lookup_context.find_all(name, paths) if templates.empty? - raise ActionView::MissingTemplate.new([paths], name, [paths], false, 'mailer') + raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer') else templates.uniq { |t| t.formats }.each(&block) end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index b06c465380..b9c56c540d 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -209,6 +209,12 @@ class BaseTest < ActiveSupport::TestCase assert_equal "New Subject!", email.subject end + test 'default subject can have interpolations' do + I18n.backend.store_translations('en', base_mailer: {with_subject_interpolations: {subject: 'Will the real %{rapper_or_impersonator} please stand up?'}}) + email = BaseMailer.with_subject_interpolations + assert_equal 'Will the real Slim Shady please stand up?', email.subject + end + test "translations are scoped properly" do I18n.backend.store_translations('en', base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}}) email = BaseMailer.email_with_translations diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index 8fca6177bd..504ca36483 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -123,4 +123,8 @@ class BaseMailer < ActionMailer::Base mail(:template_name => "welcome") nil end + + def with_subject_interpolations + mail(subject: default_i18n_subject(rapper_or_impersonator: 'Slim Shady'), body: '') + end end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index a3d2274b61..6940683c8c 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,9 +1,104 @@ ## Rails 4.0.0 (unreleased) ## -* `BestStandardsSupport` no longer duplicates `X-UA-Compatible` values on - each request to prevent header size from blowing up. +* Change `image_alt` method to replace underscores/hyphens to spaces in filenames. - *Edward Anderson* + Previously, underscored filenames became `alt="A_long_file_name_with_underscores"` + in HTML, which is poor for accessibility. For instance, Apple's VoiceOver Utility + pronounces each underscore. `A_long_file_name` thus would be read as `A underscore + long underscore file underscore name.` Now underscored or hyphenated filenames + (both of which are very popular naming conventions) read more naturally in + screen readers by converting both hyphens and underscores to spaces. + + Before: + image_tag('underscored_file_name.png') + # => <img alt="Underscored_file_name" src="/assets/underscored_file_name.png" /> + + After: + image_tag('underscored_file_name.png') + # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" /> + + *Nick Cox* + +* We don't support the `:controller` option for route definitions + with the ruby constant notation. This will now result in an + `ArgumentError`. + + Example: + # This raises an ArgumentError: + resources :posts, :controller => "Admin::Posts" + + # Use directory notation instead: + resources :posts, :controller => "admin/posts" + + *Yves Senn* + +* `assert_template` can be used to verify the locals of partials, + which live inside a directory. + Fixes #8516. + + # Prefixed partials inside directories worked and still work. + assert_template partial: 'directory/_partial', locals: {name: 'John'} + + # This did not work but does now. + assert_template partial: 'directory/partial', locals: {name: 'John'} + + *Yves Senn* + +* Fix `content_tag_for` with array html option. + It would embed array as string instead of joining it like `content_tag` does: + + content_tag(:td, class: ["foo", "bar"]){} + #=> '<td class="foo bar"></td>' + + Before: + + content_tag_for(:td, item, class: ["foo", "bar"]) + #=> '<td class="item ["foo", "bar"]" id="item_1"></td>' + + After: + + content_tag_for(:td, item, class: ["foo", "bar"]) + #=> '<td class="item foo bar" id="item_1"></td>' + + *Semyon Perepelitsa* + +* Remove `BestStandardsSupport` middleware, !DOCTYPE html already triggers + standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx + and ChromeFrame header has been moved to `config.action_dispatch.default_headers` + + *Guillermo Iguaran* + +* Fix CSRF protection and `current_url?` helper to work with HEAD requests + now that `ActionDispatch::Head` has been removed in favor of `Rack::Head`. + + *Michiel Sikkes* + +* Change `asset_path` to not include `SCRIPT_NAME` when it's used + from a mounted engine. Fixes #8119. + + *Piotr Sarnacki* + +* Add javascript based routing path matcher to `/rails/info/routes`. + Routes can now be filtered by whether or not they match a path. + + *Richard Schneeman* + +* Given + + params.permit(:name) + + `:name` passes if it is a key of `params` whose value is a permitted scalar. + + Similarly, given + + params.permit(tags: []) + + `:tags` passes if it is a key of `params` whose value is an array of + permitted scalars. + + Permitted scalars filtering happens at any level of nesting. + + *Xavier Noria* * Change the behavior of route defaults so that explicit defaults are no longer required where the key is not part of the path. For example: @@ -197,7 +292,8 @@ * More descriptive error messages when calling `render :partial` with an invalid `:layout` argument. - #8376 + + Fixes #8376. render partial: 'partial', layout: true @@ -492,10 +588,13 @@ *Richard Schneeman* -* Deprecate availbility of `ActionView::RecordIdentifier` in controllers by default. - It's view specific and can be easily included in controller manually if someone - really needs it. RecordIdentifier will be removed from `ActionController::Base` - in Rails 4.1. *Piotr Sarnacki* +* Deprecate availability of `ActionView::RecordIdentifier` in controllers by default. + It's view specific and can be easily included in controllers manually if someone + really needs it. Also deprecate calling `ActionController::RecordIdentifier.dom_id` and + `dom_class` directly, in favor of `ActionView::RecordIdentifier.dom_id` and `dom_class`. + `RecordIdentifier` will be removed from `ActionController::Base` in Rails 4.1. + + *Piotr Sarnacki* * Fix `ActionView::RecordIdentifier` to work as a singleton. *Piotr Sarnacki* @@ -726,7 +825,7 @@ * Removed old text helper apis from `highlight`, `excerpt` and `word_wrap`. *Jeremy Walker* * Templates without a handler extension now raises a deprecation warning but still - defaults to ERb. In future releases, it will simply return the template contents. *Steve Klabnik* + defaults to ERB. In future releases, it will simply return the template contents. *Steve Klabnik* * Deprecate `:disable_with` in favor of `data: { disable_with: "Text" }` option from `submit_tag`, `button_tag` and `button_to` helpers. diff --git a/actionpack/RUNNING_UNIT_TESTS b/actionpack/RUNNING_UNIT_TESTS.rdoc index 1b29abd2d1..1b29abd2d1 100644 --- a/actionpack/RUNNING_UNIT_TESTS +++ b/actionpack/RUNNING_UNIT_TESTS.rdoc diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index f00fc46b2e..03eeb841ee 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,8 +21,8 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'builder', '~> 3.1.0' - s.add_dependency 'rack', '~> 1.4.3' - s.add_dependency 'rack-test', '~> 0.6.1' + s.add_dependency 'rack', '~> 1.5.2' + s.add_dependency 'rack-test', '~> 0.6.2' s.add_dependency 'erubis', '~> 2.7.0' s.add_development_dependency 'activemodel', version diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index b6c484d188..db48022b9f 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -1,5 +1,13 @@ module AbstractController module Translation + # Delegates to <tt>I18n.translate</tt>. Also aliased as <tt>t</tt>. + # + # When the given key starts with a period, it will be scoped by the current + # controller and action. So if you call <tt>translate(".foo")</tt> from + # <tt>PeopleController#index</tt>, it will convert the call to + # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive + # to translate many keys within the same controller / action and gives you a + # simple framework for scoping them consistently. def translate(*args) key = args.first if key.is_a?(String) && (key[0] == '.') @@ -11,6 +19,7 @@ module AbstractController end alias :t :translate + # Delegates to <tt>I18n.localize</tt>. Also aliased as <tt>l</tt>. def localize(*args) I18n.localize(*args) end diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index cf2cda039d..ea33d975ef 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -82,10 +82,6 @@ module ActionController end end - def caching_allowed? - request.get? && response.status == 200 - end - def view_cache_dependencies self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 832dec7b2a..143b3e0cbd 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/extract_options' require 'action_dispatch/middleware/stack' module ActionController diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index eddee08545..6e0cd51d8b 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/keys' + module ActionController module ConditionalGet extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index d04fbae150..93568da9ef 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/extract_options' require 'abstract_controller/collector' module ActionController #:nodoc: diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 59b91a240e..e9031f3fac 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -88,7 +88,7 @@ module ActionController # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") # characters; and is terminated by a colon (":"). # The protocol relative scheme starts with a double slash "//" - when %r{^(\w[\w+.-]*:|//).*} + when %r{\A(\w[\w+.-]*:|//).*} options when String request.protocol + request.host_with_port + options diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index c5db0cb0d4..17379cf7ac 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -102,15 +102,16 @@ module ActionController #:nodoc: # This is the method that defines the application behavior when a request is found to be unverified. def handle_unverified_request - request.session = NullSessionHash.new + 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 class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: - def initialize - super(nil, nil) + def initialize(env) + super(nil, env) + @data = {} @loaded = true end @@ -125,7 +126,7 @@ module ActionController #:nodoc: host = request.host secure = request.ssl? - new(key_generator, host, secure) + new(key_generator, host, secure, options_for_env({})) end def write(*) @@ -162,11 +163,11 @@ module ActionController #:nodoc: # Returns true or false if a request is verified. Checks: # - # * is it a GET request? Gets should be safe and idempotent + # * is it a GET or HEAD request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? # * Does the X-CSRF-Token header match the form_authenticity_token def verified_request? - !protect_against_forgery? || request.get? || + !protect_against_forgery? || request.get? || request.head? || form_authenticity_token == params[request_forgery_protection_token] || form_authenticity_token == request.headers['X-CSRF-Token'] end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index e6e58ce6cd..7e720ca6f5 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' +require 'action_dispatch/http/upload' module ActionController # Raised when a required parameter is missing. @@ -19,6 +20,20 @@ module ActionController end end + # Raised when a supplied parameter is not expected. + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unexpected keys: a, b + class UnpermittedParameters < IndexError + attr_reader :params # :nodoc: + + def initialize(params) # :nodoc: + @params = params + super("found unpermitted parameters: #{params.join(", ")}") + end + end + # == Action Controller \Parameters # # Allows to choose which attributes should be whitelisted for mass updating @@ -43,10 +58,15 @@ module ActionController # Person.first.update!(permitted) # # => #<Person id: 1, name: "Francesco", age: 22, role: "user"> # - # It provides a +permit_all_parameters+ option that controls the top-level - # behavior of new instances. If it's +true+, all the parameters will be - # permitted by default. The default value for +permit_all_parameters+ - # option is +false+. + # It provides two options that controls the top-level behavior of new instances: + # + # * +permit_all_parameters+ - If it's +true+, all the parameters will be + # permitted by default. The default is +false+. + # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters + # that are not explicitly permitted are found. The values can be <tt>:log</tt> to + # write a message on the logger or <tt>:raise</tt> to raise + # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt> + # in test and development environments, +false+ otherwise. # # params = ActionController::Parameters.new # params.permitted? # => false @@ -56,6 +76,16 @@ module ActionController # params = ActionController::Parameters.new # params.permitted? # => true # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => {} + # + # ActionController::Parameters.action_on_unpermitted_parameters = :raise + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b + # # <tt>ActionController::Parameters</tt> is inherited from # <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means # that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>. @@ -65,6 +95,11 @@ module ActionController # params["key"] # => "value" class Parameters < ActiveSupport::HashWithIndifferentAccess cattr_accessor :permit_all_parameters, instance_accessor: false + cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false + + # Never raise an UnpermittedParameters exception because of these params + # are present. They are added by Rails and it's of no concern. + NEVER_UNPERMITTED_PARAMS = %w( controller action ) # Returns a new instance of <tt>ActionController::Parameters</tt>. # Also, sets the +permitted+ attribute to the default value of @@ -150,6 +185,21 @@ module ActionController # permitted.has_key?(:age) # => true # permitted.has_key?(:role) # => false # + # Only permitted scalars pass the filter. For example, given + # + # params.permit(:name) + # + # +:name+ passes it is a key of +params+ whose associated value is of type + # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, + # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, or + # +ActionDispatch::Http::UploadedFile+. Otherwise, the key +:name+ is + # filtered out. + # + # You may declare that the parameter should be an array of permitted scalars + # by mapping it to an empty array: + # + # params.permit(tags: []) + # # You can also use +permit+ on nested parameters, like: # # params = ActionController::Parameters.new({ @@ -196,32 +246,15 @@ module ActionController filters.flatten.each do |filter| case filter - when Symbol, String then - if has_key?(filter) - _value = self[filter] - params[filter] = _value unless Hash === _value - end - keys.grep(/\A#{Regexp.escape(filter)}\(\d+[if]?\)\z/) { |key| params[key] = self[key] } + when Symbol, String + permitted_scalar_filter(params, filter) when Hash then - filter = filter.with_indifferent_access - - self.slice(*filter.keys).each do |key, values| - return unless values - - key = key.to_sym - - params[key] = each_element(values) do |value| - # filters are a Hash, so we expect value to be a Hash too - next if filter.is_a?(Hash) && !value.is_a?(Hash) - - value = self.class.new(value) if !value.respond_to?(:permit) - - value.permit(*Array.wrap(filter[key])) - end - end + hash_filter(params, filter) end end + unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters + params.permit! end @@ -300,6 +333,99 @@ module ActionController yield object end end + + def unpermitted_parameters!(params) + unpermitted_keys = unpermitted_keys(params) + if unpermitted_keys.any? + case self.class.action_on_unpermitted_parameters + when :log + ActionController::Base.logger.debug "Unpermitted parameters: #{unpermitted_keys.join(", ")}" + when :raise + raise ActionController::UnpermittedParameters.new(unpermitted_keys) + end + end + end + + def unpermitted_keys(params) + self.keys - params.keys - NEVER_UNPERMITTED_PARAMS + end + + # + # --- Filtering ---------------------------------------------------------- + # + + # This is a white list of permitted scalar types that includes the ones + # supported in XML and JSON requests. + # + # This list is in particular used to filter ordinary requests, String goes + # as first element to quickly short-circuit the common case. + # + # If you modify this collection please update the API of +permit+ above. + PERMITTED_SCALAR_TYPES = [ + String, + Symbol, + NilClass, + Numeric, + TrueClass, + FalseClass, + Date, + Time, + # DateTimes are Dates, we document the type but avoid the redundant check. + StringIO, + IO, + ActionDispatch::Http::UploadedFile, + ] + + def permitted_scalar?(value) + PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)} + end + + def permitted_scalar_filter(params, key) + if has_key?(key) && permitted_scalar?(self[key]) + params[key] = self[key] + end + + keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k| + if permitted_scalar?(self[k]) + params[k] = self[k] + end + end + end + + def array_of_permitted_scalars?(value) + if value.is_a?(Array) + value.all? {|element| permitted_scalar?(element)} + end + end + + def array_of_permitted_scalars_filter(params, key) + if has_key?(key) && array_of_permitted_scalars?(self[key]) + params[key] = self[key] + end + end + + EMPTY_ARRAY = [] + def hash_filter(params, filter) + filter = filter.with_indifferent_access + + # Slicing filters out non-declared keys. + slice(*filter.keys).each do |key, value| + return unless value + + if filter[key] == EMPTY_ARRAY + # Declaration { comment_ids: [] }. + array_of_permitted_scalars_filter(params, key) + else + # Declaration { user: :name } or { user: [:name, :age, { adress: ... }] }. + params[key] = each_element(value) do |element| + if element.is_a?(Hash) + element = self.class.new(element) unless element.respond_to?(:permit) + element.permit(*Array.wrap(filter[key])) + end + end + end + end + end end # == Strong \Parameters diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 3e44155f73..5379547c57 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -20,22 +20,27 @@ module ActionController end initializer "action_controller.parameters_config" do |app| - ActionController::Parameters.permit_all_parameters = app.config.action_controller.delete(:permit_all_parameters) { false } + options = app.config.action_controller + + ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false } + ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do + (Rails.env.test? || Rails.env.development?) ? :log : false + end end initializer "action_controller.set_configs" do |app| paths = app.config.paths options = app.config.action_controller - options.logger ||= Rails.logger - options.cache_store ||= Rails.cache + options.logger ||= Rails.logger + options.cache_store ||= Rails.cache - options.javascripts_dir ||= paths["public/javascripts"].first - options.stylesheets_dir ||= paths["public/stylesheets"].first + options.javascripts_dir ||= paths["public/javascripts"].first + options.stylesheets_dir ||= paths["public/stylesheets"].first # Ensure readers methods get compiled - options.asset_host ||= app.config.asset_host - options.relative_url_root ||= app.config.relative_url_root + options.asset_host ||= app.config.asset_host + options.relative_url_root ||= app.config.relative_url_root ActiveSupport.on_load(:action_controller) do include app.routes.mounted_helpers diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 70e0a820c4..d598bac467 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -2,17 +2,29 @@ require 'action_view/record_identifier' module ActionController module RecordIdentifier - MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' + - 'If you would like to use it in controllers, please include ' + - 'ActionView::RecordIdentifier module.' + MODULE_MESSAGE = 'Calling ActionController::RecordIdentifier.%s is deprecated and ' \ + 'will be removed in Rails 4.1, please call using ActionView::RecordIdentifier instead.' + INSTANCE_MESSAGE = '%s method will no longer be included by default in controllers ' \ + 'since Rails 4.1. If you would like to use it in controllers, please include ' \ + 'ActionView::RecordIdentifier module.' def dom_id(record, prefix = nil) - ActiveSupport::Deprecation.warn('dom_id ' + MESSAGE) + ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_id') ActionView::RecordIdentifier.dom_id(record, prefix) end def dom_class(record, prefix = nil) - ActiveSupport::Deprecation.warn('dom_class ' + MESSAGE) + ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_class') + ActionView::RecordIdentifier.dom_class(record, prefix) + end + + def self.dom_id(record, prefix = nil) + ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_id') + ActionView::RecordIdentifier.dom_id(record, prefix) + end + + def self.dom_class(record, prefix = nil) + ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_class') ActionView::RecordIdentifier.dom_class(record, prefix) end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 331d15d403..bba1f1e201 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,6 +1,7 @@ require 'rack/session/abstract/id' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/module/anonymous' +require 'active_support/core_ext/hash/keys' module ActionController module TemplateAssertions @@ -81,8 +82,7 @@ module ActionController # # assert that the "_customer" partial was rendered with a specific object # assert_template partial: '_customer', locals: { customer: @customer } def assert_template(options = {}, message = nil) - # Force body to be read in case the - # template is being streamed + # Force body to be read in case the template is being streamed. response.body case options @@ -126,7 +126,11 @@ module ActionController if expected_partial = options[:partial] if expected_locals = options[:locals] if defined?(@_rendered_views) - view = expected_partial.to_s.sub(/^_/,'') + view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/') + + partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view + assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg + msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial, expected_locals, @_rendered_views.locals_for(view)] @@ -236,18 +240,39 @@ module ActionController end end + # Methods #destroy and #load! are overridden to avoid calling methods on the + # @store object, which does not exist for the TestSession class. class TestSession < Rack::Session::Abstract::SessionHash #:nodoc: DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS def initialize(session = {}) super(nil, nil) - replace(session.stringify_keys) + @id = SecureRandom.hex(16) + @data = stringify_keys(session) @loaded = true end def exists? true end + + def keys + @data.keys + end + + def values + @data.values + end + + def destroy + clear + end + + private + + def load! + @id + end end # Superclass for ActionController functional tests. Functional tests allow you to diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 9ab048b756..618e2f3033 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -47,7 +47,6 @@ module ActionDispatch autoload_under 'middleware' do autoload :RequestId - autoload :BestStandardsSupport autoload :Callbacks autoload :Cookies autoload :DebugExceptions diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 57660e93c4..89a7b12818 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -122,7 +122,7 @@ module ActionDispatch def valid_accept_header (xhr? && (accept || content_mime_type)) || - (accept && accept !~ BROWSER_LIKE_ACCEPTS) + (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) end def use_accept_header diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 25edd196c3..446862aad0 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -55,7 +55,7 @@ module ActionDispatch # should really prevent that from happening def encode_params(params) if params.is_a?(String) - return params.force_encoding("UTF-8").encode! + return params.force_encoding(Encoding::UTF_8).encode! elsif !params.is_a?(Hash) return params end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 91cf4784db..ca1ace4537 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -137,6 +137,7 @@ module ActionDispatch # :nodoc: @committed end + # Sets the HTTP status code. def status=(status) @status = Rack::Utils.status_code(status) end @@ -145,23 +146,31 @@ module ActionDispatch # :nodoc: @content_type = content_type.to_s end - # The response code of the request + # The response code of the request. def response_code @status end - # Returns a String to ensure compatibility with Net::HTTPResponse + # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>. def code @status.to_s end + # Returns the corresponding message for the current HTTP status code: + # + # response.status = 200 + # response.message # => "OK" + # + # response.status = 404 + # response.message # => "Not Found" + # def message Rack::Utils::HTTP_STATUS_CODES[@status] end alias_method :status_message, :message def respond_to?(method) - if method.to_sym == :to_path + if method.to_s == 'to_path' stream.respond_to?(:to_path) else super @@ -172,6 +181,8 @@ module ActionDispatch # :nodoc: stream.to_path end + # Returns the content of the response as a string. This contains the contents + # of any calls to <tt>render</tt>. def body strings = [] each { |part| strings << part.to_s } @@ -180,6 +191,7 @@ module ActionDispatch # :nodoc: EMPTY = " " + # Allows you to manually set or override the response body. def body=(body) @blank = true if body == EMPTY diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 8a97248eb3..67cb7fbcb5 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -70,7 +70,7 @@ module ActionDispatch def encode_filename(filename) # Encode the filename in the utf8 encoding, unless it is nil - filename.force_encoding("UTF-8").encode! if filename + filename.force_encoding(Encoding::UTF_8).encode! if filename end end diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb deleted file mode 100644 index 94efeb79fa..0000000000 --- a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb +++ /dev/null @@ -1,30 +0,0 @@ -module ActionDispatch - class BestStandardsSupport - def initialize(app, type = true) - @app = app - - @header = case type - when true - "IE=Edge,chrome=1" - when :builtin - "IE=Edge" - when false - nil - end - end - - def call(env) - status, headers, body = @app.call(env) - - if headers["X-UA-Compatible"] && @header - unless headers["X-UA-Compatible"][@header] - headers["X-UA-Compatible"] << "," << @header.to_s - end - else - headers["X-UA-Compatible"] = @header - end - - [status, headers, body] - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 6ecbb03784..fff26bd1b2 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/key_generator' require 'active_support/message_verifier' module ActionDispatch @@ -110,13 +111,17 @@ module ActionDispatch # $& => example.local DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ + def self.options_for_env(env) #:nodoc: + { signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '', + encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '', + encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '', + token_key: env[TOKEN_KEY] } + end + def self.build(request) env = request.env key_generator = env[GENERATOR_KEY] - options = { signed_cookie_salt: env[SIGNED_COOKIE_SALT], - encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT], - encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT], - token_key: env[TOKEN_KEY] } + options = options_for_env env host = request.host secure = request.ssl? @@ -145,6 +150,10 @@ module ActionDispatch @cookies[name.to_s] end + def fetch(name, *args, &block) + @cookies.fetch(name.to_s, *args, &block) + end + def key?(name) @cookies.key?(name.to_s) end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 7c12590c49..84df55fd5a 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -26,7 +26,7 @@ module ActionDispatch def generate_sid sid = SecureRandom.hex(16) - sid.encode!('UTF-8') + sid.encode!(Encoding::UTF_8) sid end diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb index 400ae97d22..24e44f31ac 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb @@ -7,7 +7,7 @@ <td data-route-verb='<%= route[:verb] %>'> <%= route[:verb] %> </td> - <td data-route-path='<%= route[:path] %>'> + <td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'> <%= route[:path] %> </td> <td data-route-reqs='<%= route[:reqs] %>'> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index 9026c4eeb2..95461fa693 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -1,22 +1,58 @@ <% content_for :style do %> - #route_table td { padding: 0 30px; } - #route_table { margin: 0 auto 0; } + #route_table { + margin: 0 auto 0; + border-collapse: collapse; + } + + #route_table td { + padding: 0 30px; + } + + #route_table tr.bottom th { + padding-bottom: 10px; + line-height: 15px; + } + + #route_table .matched_paths { + background-color: LightGoldenRodYellow; + } + + #route_table .matched_paths { + border-bottom: solid 3px SlateGrey; + } + + #path_search { + width: 80%; + font-size: inherit; + } <% end %> <table id='route_table' class='route_table'> <thead> <tr> - <th>Helper<br /> + <th>Helper</th> + <th>HTTP Verb</th> + <th>Path</th> + <th>Controller#Action</th> + </tr> + <tr class='bottom'> + <th><%# Helper %> <%= link_to "Path", "#", 'data-route-helper' => '_path', title: "Returns a relative path (without the http or domain)" %> / <%= link_to "Url", "#", 'data-route-helper' => '_url', - title: "Returns an absolute url (with the http and domain)" %> + title: "Returns an absolute url (with the http and domain)" %> + </th> + <th><%# HTTP Verb %> + </th> + <th><%# Path %> + <%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %> + </th> + <th><%# Controller#action %> </th> - <th>HTTP Verb</th> - <th>Path</th> - <th>Controller#Action</th> </tr> </thead> + <tbody class='matched_paths' id='matched_paths'> + </tbody> <tbody> <%= yield %> </tbody> @@ -25,7 +61,7 @@ <script type='text/javascript'> function each(elems, func) { if (!elems instanceof Array) { elems = [elems]; } - for (var i = elems.length; i--; ) { + for (var i = 0, len = elems.length; i < len; i++) { func(elems[i]); } } @@ -46,11 +82,63 @@ function setupRouteToggleHelperLinks() { var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]'); onClick(toggleLinks, function(){ - var helperTxt = this.getAttribute("data-route-helper"); - var helperElems = document.querySelectorAll('[data-route-name] span.helper'); + var helperTxt = this.getAttribute("data-route-helper"), + helperElems = document.querySelectorAll('[data-route-name] span.helper'); setValOn(helperElems, helperTxt); }); } + // takes an array of elements with a data-regexp attribute and + // passes their their parent <tr> into the callback function + // if the regexp matchs a given path + function eachElemsForPath(elems, path, func) { + each(elems, function(e){ + var reg = e.getAttribute("data-regexp"); + if (path.match(RegExp(reg))) { + func(e.parentNode.cloneNode(true)); + } + }) + } + + // Ensure path always starts with a slash "/" and remove params or fragments + function sanitizePath(path) { + var path = path.charAt(0) == '/' ? path : "/" + path; + return path.replace(/\#.*|\?.*/, ''); + } + + // Enables path search functionality + function setupMatchPaths() { + var regexpElems = document.querySelectorAll('#route_table [data-regexp]'), + pathElem = document.querySelector('#path_search'), + selectedSection = document.querySelector('#matched_paths'), + noMatchText = '<tr><th colspan="4">None</th></tr>'; + + + // Remove matches if no path is present + pathElem.onblur = function(e) { + if (pathElem.value === "") selectedSection.innerHTML = ""; + } + + // On key press perform a search for matching paths + pathElem.onkeyup = function(e){ + var path = sanitizePath(pathElem.value), + defaultText = '<tr><th colspan="4">Paths Matching (' + path + '):</th></tr>'; + + // Clear out results section + selectedSection.innerHTML= defaultText; + + // Display matches if they exist + eachElemsForPath(regexpElems, path, function(e){ + selectedSection.appendChild(e); + }); + + // If no match present, tell the user + if (selectedSection.innerHTML === defaultText) { + selectedSection.innerHTML = selectedSection.innerHTML + noMatchText; + } + } + } + + setupMatchPaths(); setupRouteToggleHelperLinks(); </script> diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 5a835ae439..edf37bb9a5 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -6,7 +6,6 @@ module ActionDispatch config.action_dispatch.x_sendfile_header = nil config.action_dispatch.ip_spoofing_check = true config.action_dispatch.show_exceptions = true - config.action_dispatch.best_standards_support = true config.action_dispatch.tld_length = 1 config.action_dispatch.ignore_accept_header = false config.action_dispatch.rescue_templates = { } @@ -21,7 +20,8 @@ module ActionDispatch config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff' + 'X-Content-Type-Options' => 'nosniff', + 'X-UA-Compatible' => 'chrome=1' } config.eager_load_namespaces << ActionDispatch diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index a05a23d953..7bc812fd22 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -63,6 +63,10 @@ module ActionDispatch @exists = nil # we haven't checked yet end + def id + options[:id] + end + def options Options.find @env end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index ea3e8357d4..bc6dd7145c 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -34,6 +34,23 @@ module ActionDispatch super.to_s end + def regexp + __getobj__.path.to_regexp + end + + def json_regexp + str = regexp.inspect. + sub('\\A' , '^'). + sub('\\Z' , '$'). + sub('\\z' , '$'). + sub(/^\// , ''). + sub(/\/[a-z]*$/ , ''). + gsub(/\(\?#.+\)/ , ''). + gsub(/\(\?-\w+:/ , '('). + gsub(/\s/ , '') + Regexp.new(str).source + end + def reqs @reqs ||= begin reqs = endpoint @@ -73,10 +90,11 @@ module ActionDispatch routes_to_display = filter_routes(filter) routes = collect_routes(routes_to_display) - formatter.section :application, 'Application routes', routes + formatter.section routes @engines.each do |name, engine_routes| - formatter.section :engine, "Routes for #{name}", engine_routes + formatter.section_title "Routes for #{name}" + formatter.section engine_routes end formatter.result @@ -100,7 +118,11 @@ module ActionDispatch end.collect do |route| collect_engine_routes(route) - { name: route.name, verb: route.verb, path: route.path, reqs: route.reqs } + { name: route.name, + verb: route.verb, + path: route.path, + reqs: route.reqs, + regexp: route.json_regexp } end end @@ -125,8 +147,11 @@ module ActionDispatch @buffer.join("\n") end - def section(type, title, routes) - @buffer << "\n#{title}:" unless type == :application + def section_title(title) + @buffer << "\n#{title}:" + end + + def section(routes) @buffer << draw_section(routes) end @@ -148,8 +173,11 @@ module ActionDispatch @buffer = [] end - def section(type, title, routes) + def section_title(title) @buffer << %(<tr><th colspan="4">#{title}</th></tr>) + end + + def section(routes) @buffer << @view.render(partial: "routes/route", collection: routes) end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6d93f609a6..0a41ed0fcf 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/enumerable' +require 'active_support/core_ext/array/extract_options' require 'active_support/inflector' require 'action_dispatch/routing/redirection' @@ -245,6 +246,12 @@ module ActionDispatch raise ArgumentError, "missing :action" end + if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/ + message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems." + message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use" + raise ArgumentError, message + end + hash = {} hash[:controller] = controller unless controller.blank? hash[:action] = action unless action.blank? @@ -1403,9 +1410,10 @@ module ActionDispatch def add_route(action, options) # :nodoc: path = path_for_action(action, options.delete(:path)) + action = action.to_s.dup - if action.to_s =~ /^[\w\/]+$/ - options[:action] ||= action unless action.to_s.include?("/") + if action =~ /^[\w\/]+$/ + options[:action] ||= action unless action.include?("/") else action = nil end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index c72310cca3..ff86f87d49 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -4,6 +4,7 @@ require 'thread_safe' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/array/extract_options' require 'action_controller/metal/exceptions' module ActionDispatch @@ -100,36 +101,16 @@ module ActionDispatch def initialize @routes = {} @helpers = [] - @module = Module.new do - protected - - def handle_positional_args(args, options, segment_keys) - inner_options = args.extract_options! - result = options.dup - - if args.size > 0 - keys = segment_keys - if args.size < keys.size - 1 # take format into account - keys -= self.url_options.keys if self.respond_to?(:url_options) - keys -= options.keys - end - result.merge!(Hash[keys.zip(args)]) - end - - result.merge!(inner_options) - end - end + @module = Module.new end def helper_names - self.module.instance_methods.map(&:to_s) + @helpers.map(&:to_s) end def clear! @helpers.each do |helper| - @module.module_eval do - remove_possible_method helper - end + @module.remove_possible_method helper end @routes.clear @@ -162,68 +143,126 @@ module ActionDispatch routes.length end - private + class UrlHelper # :nodoc: + def self.create(route, options) + if optimize_helper?(route) + OptimizedUrlHelper.new(route, options) + else + new route, options + end + end - def define_named_route_methods(name, route) - define_url_helper route, :"#{name}_path", - route.defaults.merge(:use_route => name, :only_path => true) - define_url_helper route, :"#{name}_url", - route.defaults.merge(:use_route => name, :only_path => false) + def self.optimize_helper?(route) + route.requirements.except(:controller, :action).empty? end - # Create a url helper allowing ordered parameters to be associated - # with corresponding dynamic segments, so you can do: - # - # foo_url(bar, baz, bang) - # - # Instead of: - # - # foo_url(bar: bar, baz: baz, bang: bang) - # - # Also allow options hash, so you can do: - # - # foo_url(bar, baz, bang, sort_by: 'baz') - # - def define_url_helper(route, name, options) - @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - remove_possible_method :#{name} - def #{name}(*args) - if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation? - options = #{options.inspect} - options.merge!(url_options) if respond_to?(:url_options) - options[:path] = "#{optimized_helper(route)}" - ActionDispatch::Http::URL.url_for(options) - else - url_for(handle_positional_args(args, #{options.inspect}, #{route.segment_keys.inspect})) - end + class OptimizedUrlHelper < UrlHelper # :nodoc: + attr_reader :arg_size + + def initialize(route, options) + super + @path_parts = @route.required_parts + @arg_size = @path_parts.size + @string_route = string_route(route) + end + + def call(t, args) + if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t) + @options.merge!(t.url_options) if t.respond_to?(:url_options) + @options[:path] = optimized_helper(args) + ActionDispatch::Http::URL.url_for(@options) + else + super end - END_EVAL + end + + private + + def string_route(route) + string_route = route.ast.to_s.dup + while string_route.gsub!(/\([^\)]*\)/, "") + true + end + string_route + end + + def optimized_helper(args) + path = @string_route.dup + klass = Journey::Router::Utils - helpers << name + @path_parts.zip(args) do |part, arg| + # Replace each route parameter + # e.g. :id for regular parameter or *path for globbing + # with ruby string interpolation code + path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(arg.to_param)) + end + path + end + + def optimize_routes_generation?(t) + t.send(:optimize_routes_generation?) + end end - # Clause check about when we need to generate an optimized helper. - def optimize_helper?(route) #:nodoc: - route.requirements.except(:controller, :action).empty? + def initialize(route, options) + @options = options + @segment_keys = route.segment_keys + @route = route + end + + def call(t, args) + t.url_for(handle_positional_args(t, args, @options, @segment_keys)) end - # Generates the interpolation to be used in the optimized helper. - def optimized_helper(route) - string_route = route.ast.to_s + def handle_positional_args(t, args, options, keys) + inner_options = args.extract_options! + result = options.dup - while string_route.gsub!(/\([^\)]*\)/, "") - true + if args.size > 0 + if args.size < keys.size - 1 # take format into account + keys -= t.url_options.keys if t.respond_to?(:url_options) + keys -= options.keys + end + result.merge!(Hash[keys.zip(args)]) end - route.required_parts.each_with_index do |part, i| - # Replace each route parameter - # e.g. :id for regular parameter or *path for globbing - # with ruby string interpolation code - string_route.gsub!(/(\*|:)#{part}/, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}") - end + result.merge!(inner_options) + end + end + + private + # Create a url helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(bar: bar, baz: baz, bang: bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, sort_by: 'baz') + # + def define_url_helper(route, name, options) + helper = UrlHelper.create(route, options.dup) - string_route + @module.remove_possible_method name + @module.module_eval do + define_method(name) do |*args| + helper.call self, args + end end + + helpers << name + end + + def define_named_route_methods(name, route) + define_url_helper route, :"#{name}_path", + route.defaults.merge(:use_route => name, :only_path => true) + define_url_helper route, :"#{name}_url", + route.defaults.merge(:use_route => name, :only_path => false) + end end attr_accessor :formatter, :set, :named_routes, :default_scope, :router diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb index 73af5920ed..e2393d3799 100644 --- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/array/extract_options' + module ActionDispatch module Routing class RoutesProxy #:nodoc: diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 11743e36f2..31e37893c6 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -182,6 +182,8 @@ module ActionView # width="30" and height="45", and "50" becomes width="50" and height="50". # <tt>:size</tt> will be ignored if the value is not in the correct format. # + # ==== Examples + # # image_tag("icon") # # => <img alt="Icon" src="/assets/icon" /> # image_tag("icon.png") @@ -212,10 +214,24 @@ module ActionView end # Returns a string suitable for an html image tag alt attribute. - # +src+ is meant to be an image file path. - # It removes the basename of the file path and the digest, if any. + # The +src+ argument is meant to be an image file path. + # The method removes the basename of the file path and the digest, + # if any. It also removes hyphens and underscores from file names and + # replaces them with spaces, returning a space-separated, titleized + # string. + # + # ==== Examples + # + # image_tag('rails.png') + # # => <img alt="Rails" src="/assets/rails.png" /> + # + # image_tag('hyphenated-file-name.png') + # # => <img alt="Hyphenated file name" src="/assets/hyphenated-file-name.png" /> + # + # image_tag('underscored_file_name.png') + # # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" /> def image_alt(src) - File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize + File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize end # Returns an html video tag for the +sources+. If +sources+ is a string, diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb index 0affac41e8..71b78cf0b5 100644 --- a/actionpack/lib/action_view/helpers/asset_url_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_url_helper.rb @@ -132,8 +132,7 @@ module ActionView source = compute_asset_path(source, options) end - relative_url_root = (defined?(config.relative_url_root) && config.relative_url_root) || - (respond_to?(:request) && request.try(:script_name)) + relative_url_root = defined?(config.relative_url_root) && config.relative_url_root if relative_url_root source = "#{relative_url_root}#{source}" unless source.starts_with?("#{relative_url_root}/") end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index cf978d8e83..61f939bff1 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -1,5 +1,6 @@ require 'date' require 'action_view/helpers/tag_helper' +require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/with_options' @@ -880,6 +881,7 @@ module ActionView def translated_date_order date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => []) + date_order = date_order.map { |element| element.to_sym } forbidden_elements = date_order - [:year, :month, :day] if forbidden_elements.any? @@ -1039,14 +1041,38 @@ module ActionView end class FormBuilder + # Wraps ActionView::Helpers::DateHelper#date_select for form builders: + # + # <%= form_for @person do |f| %> + # <%= f.date_select :birth_date %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def date_select(method, options = {}, html_options = {}) @template.date_select(@object_name, method, objectify_options(options), html_options) end + # Wraps ActionView::Helpers::DateHelper#time_select for form builders: + # + # <%= form_for @race do |f| %> + # <%= f.time_select :average_lap %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def time_select(method, options = {}, html_options = {}) @template.time_select(@object_name, method, objectify_options(options), html_options) end + # Wraps ActionView::Helpers::DateHelper#datetime_select for form builders: + # + # <%= form_for @person do |f| %> + # <%= f.time_select :last_request_at %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def datetime_select(method, options = {}, html_options = {}) @template.datetime_select(@object_name, method, objectify_options(options), html_options) end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 4a31de715d..3dae1fc87a 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -8,7 +8,6 @@ require 'action_view/model_naming' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/string/inflections' module ActionView @@ -771,8 +770,8 @@ module ActionView # text_field(:post, :title, class: "create_input") # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> # - # text_field(:session, :user, onchange: "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }") - # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }"/> + # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }") + # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }"/> # # text_field(:snippet, :code, size: 20, class: 'code_input') # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> @@ -792,8 +791,8 @@ module ActionView # password_field(:account, :secret, class: "form_input", value: @account.secret) # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" /> # - # password_field(:user, :password, onchange: "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }") - # # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/> + # password_field(:user, :password, onchange: "if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }") + # # => <input type="password" id="user_password" name="user[password]" onchange="if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }"/> # # password_field(:account, :pin, size: 20, class: 'form_input') # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" /> diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index ae7bfd1ec6..8b3a37a853 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -2,6 +2,7 @@ require 'cgi' require 'erb' require 'action_view/helpers/form_helper' require 'active_support/core_ext/string/output_safety' +require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/wrap' module ActionView @@ -756,26 +757,74 @@ module ActionView end class FormBuilder + # Wraps ActionView::Helpers::FormOptionsHelper#select for form builders: + # + # <%= form_for @post do |f| %> + # <%= f.select :person_id, Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }) %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def select(method, choices, options = {}, html_options = {}) @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options)) end + # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders: + # + # <%= form_for @post do |f| %> + # <%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) end + # Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders: + # + # <%= form_for @city do |f| %> + # <%= f.grouped_collection_select :country_id, :country_id, @continents, :countries, :name, :id, :name %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options)) end + # Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders: + # + # <%= form_for @user do |f| %> + # <%= f.time_zone_select :time_zone, nil, include_blank: true %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end + # Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders: + # + # <%= form_for @post do |f| %> + # <%= f.collection_check_boxes :author_ids, Author.all, :id, :name_with_initial %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block) @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) end + # Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders: + # + # <%= form_for @post do |f| %> + # <%= f.collection_radio_buttons :author_id, Author.all, :id, :name_with_initial %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block) @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index cfdd7c77d8..878d3e0eda 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -13,8 +13,8 @@ module ActionView "'" => "\\'" } - JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '
' - JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '
' + JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '
' + JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '
' # Escapes carriage returns and single and double quotes for JavaScript segments. # diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 271a194913..f767957fa9 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -92,7 +92,7 @@ module ActionView # for each record. def content_tag_for_single_record(tag_name, record, prefix, options, &block) options = options ? options.dup : {} - options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip + options[:class] = [ dom_class(record, prefix), options[:class] ].compact options[:id] = dom_id(record, prefix) if block_given? diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb index d27df45b5a..9655008fe2 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -13,13 +13,13 @@ module ActionView end end - def render + def render(&block) rendered_collection = render_collection do |item, value, text, default_html_options| default_html_options[:multiple] = true builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options) if block_given? - yield builder + @template_object.capture(builder, &block) else render_component(builder) end diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb index 81f2ecb2b3..893f4411e7 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb +++ b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb @@ -13,12 +13,12 @@ module ActionView end end - def render + def render(&block) render_collection do |item, value, text, default_html_options| builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options) if block_given? - yield builder + @template_object.capture(builder, &block) else render_component(builder) end diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb index 380d6d3686..734591394b 100644 --- a/actionpack/lib/action_view/helpers/tags/date_select.rb +++ b/actionpack/lib/action_view/helpers/tags/date_select.rb @@ -27,7 +27,7 @@ module ActionView end def datetime_selector(options, html_options) - datetime = options[:selected] || value(object) || default_datetime(options) + datetime = options.fetch(:selected) { value(object) || default_datetime(options) } @auto_index ||= nil options = options.dup diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index bade121d44..5e20b557d8 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -514,7 +514,7 @@ module ActionView "in a #request method" end - return false unless request.get? + return false unless request.get? || request.head? url_string = url_for(options) diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index b927c69260..f73d14c79b 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -80,8 +80,7 @@ module ActionView # problems with converting the user's data to # the <tt>default_internal</tt>. # - # To do so, simply raise the raise +WrongEncodingError+ - # as follows: + # To do so, simply raise +WrongEncodingError+ as follows: # # raise WrongEncodingError.new( # problematic_string, diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index b479f991bc..a89d51221e 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -17,7 +17,7 @@ module ActionView end def message - @string.force_encoding("BINARY") + @string.force_encoding(Encoding::ASCII_8BIT) "Your template was not saved as valid #{@encoding}. Please " \ "either specify #{@encoding} as the encoding for your template " \ "in your text editor, or mark the template with its " \ diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index afbbece90f..5aaafc15c1 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -81,7 +81,7 @@ module ActionView # wrong, we can still find an encoding tag # (<%# encoding %>) inside the String using a regular # expression - template_source = template.source.dup.force_encoding("BINARY") + template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT) erb = template_source.gsub(ENCODING_TAG, '') encoding = $2 diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 4479da5bc4..463f192d0c 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -122,7 +122,7 @@ module ActionView class RenderedViewsCollection def initialize - @rendered_views ||= {} + @rendered_views ||= Hash.new { |hash, key| hash[key] = [] } end def add(view, locals) @@ -134,6 +134,10 @@ module ActionView @rendered_views[view] end + def rendered_views + @rendered_views.keys + end + def view_rendered?(view, expected_locals) locals_for(view).any? do |actual_locals| expected_locals.all? {|key, value| value == actual_locals[key] } diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb index 99064a8b87..4fdc480b43 100644 --- a/actionpack/test/abstract/translation_test.rb +++ b/actionpack/test/abstract/translation_test.rb @@ -1,39 +1,50 @@ require 'abstract_unit' -# class TranslatingController < ActionController::Base -# end - -class TranslationControllerTest < ActiveSupport::TestCase - def setup - @controller = ActionController::Base.new - end - - def test_action_controller_base_responds_to_translate - assert_respond_to @controller, :translate - end - - def test_action_controller_base_responds_to_t - assert_respond_to @controller, :t - end - - def test_action_controller_base_responds_to_localize - assert_respond_to @controller, :localize - end - - def test_action_controller_base_responds_to_l - assert_respond_to @controller, :l - end - - def test_lazy_lookup - expected = 'bar' - @controller.stubs(:action_name => :index) - I18n.stubs(:translate).with('action_controller.base.index.foo').returns(expected) - assert_equal expected, @controller.t('.foo') - end - - def test_default_translation - key, expected = 'one.two' 'bar' - I18n.stubs(:translate).with(key).returns(expected) - assert_equal expected, @controller.t(key) +module AbstractController + module Testing + class TranslationController < AbstractController::Base + include AbstractController::Translation + end + + class TranslationControllerTest < ActiveSupport::TestCase + def setup + @controller = TranslationController.new + end + + def test_action_controller_base_responds_to_translate + assert_respond_to @controller, :translate + end + + def test_action_controller_base_responds_to_t + assert_respond_to @controller, :t + end + + def test_action_controller_base_responds_to_localize + assert_respond_to @controller, :localize + end + + def test_action_controller_base_responds_to_l + assert_respond_to @controller, :l + end + + def test_lazy_lookup + expected = 'bar' + @controller.stubs(action_name: :index) + I18n.stubs(:translate).with('abstract_controller.testing.translation.index.foo').returns(expected) + assert_equal expected, @controller.t('.foo') + end + + def test_default_translation + key, expected = 'one.two', 'bar' + I18n.stubs(:translate).with(key).returns(expected) + assert_equal expected, @controller.t(key) + end + + def test_localize + time, expected = Time.gm(2000), 'Sat, 01 Jan 2000 00:00:00 +0000' + I18n.stubs(:localize).with(time).returns(expected) + assert_equal expected, @controller.l(time) + end + end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index bbcd289886..7157bccfb3 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -38,6 +38,8 @@ end ActiveSupport::Dependencies.hook! +Thread.abort_on_exception = true + # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/actionpack/test/activerecord/form_helper_activerecord_test.rb b/actionpack/test/activerecord/form_helper_activerecord_test.rb new file mode 100644 index 0000000000..2e302c65a7 --- /dev/null +++ b/actionpack/test/activerecord/form_helper_activerecord_test.rb @@ -0,0 +1,91 @@ +require 'active_record_unit' +require 'fixtures/project' +require 'fixtures/developer' + +class FormHelperActiveRecordTest < ActionView::TestCase + tests ActionView::Helpers::FormHelper + + def form_for(*) + @output_buffer = super + end + + def setup + @developer = Developer.new + @developer.id = 123 + @developer.name = "developer #123" + + @project = Project.new + @project.id = 321 + @project.name = "project #321" + @project.save + + @developer.projects << @project + @developer.save + end + + def teardown + Project.delete(321) + Developer.delete(123) + end + + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw do + resources :developers do + resources :projects + end + end + + def _routes + Routes + end + + include Routes.url_helpers + + def test_nested_fields_for_with_child_index_option_override_on_a_nested_attributes_collection_association + form_for(@developer) do |f| + concat f.fields_for(:projects, @developer.projects.first, :child_index => 'abc') { |cf| + concat cf.text_field(:name) + } + end + + expected = whole_form('/developers/123', 'edit_developer_123', 'edit_developer', :method => 'patch') do + '<input id="developer_projects_attributes_abc_name" name="developer[projects_attributes][abc][name]" type="text" value="project #321" />' + + '<input id="developer_projects_attributes_abc_id" name="developer[projects_attributes][abc][id]" type="hidden" value="321" />' + end + + assert_dom_equal expected, output_buffer + end + + protected + + def hidden_fields(method = nil) + txt = %{<div style="margin:0;padding:0;display:inline">} + txt << %{<input name="utf8" type="hidden" value="✓" />} + if method && !%w(get post).include?(method.to_s) + txt << %{<input name="_method" type="hidden" value="#{method}" />} + end + txt << %{</div>} + end + + def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) + txt = %{<form accept-charset="UTF-8" action="#{action}"} + txt << %{ enctype="multipart/form-data"} if multipart + txt << %{ data-remote="true"} if remote + txt << %{ class="#{html_class}"} if html_class + txt << %{ id="#{id}"} if id + method = method.to_s == "get" ? "get" : "post" + txt << %{ method="#{method}">} + end + + def whole_form(action = "/", id = nil, html_class = nil, options = nil) + contents = block_given? ? yield : "" + + if options.is_a?(Hash) + method, remote, multipart = options.values_at(:method, :remote, :multipart) + else + method = options + end + + form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" + end +end
\ No newline at end of file diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index e2239c05c7..72b882539c 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -751,13 +751,17 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest assert_equal "http://bar.com/foo", foos_url end - test "test can override default url options" do + def test_can_override_default_url_options + original_host = default_url_options.dup + default_url_options[:host] = "foobar.com" assert_equal "http://foobar.com/foo", foos_url get "/bar" assert_response :success assert_equal "http://foobar.com/foo", foos_url + ensure + ActionDispatch::Integration::Session.default_url_options = self.default_url_options = original_host end test "current request path parameters are recalled" do diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 94a8d2f180..71bcfd664e 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'rbconfig' +require 'active_support/core_ext/array/extract_options' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. @@ -80,7 +81,7 @@ end class StreamingLayoutController < LayoutTest def render(*args) - options = args.extract_options! || {} + options = args.extract_options! super(*args, options.merge(:stream => true)) end end diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb index 41ff2f3809..bac1d02977 100644 --- a/actionpack/test/controller/localized_templates_test.rb +++ b/actionpack/test/controller/localized_templates_test.rb @@ -9,14 +9,20 @@ class LocalizedTemplatesTest < ActionController::TestCase tests LocalizedController def test_localized_template_is_used + old_locale = I18n.locale I18n.locale = :de get :hello_world assert_equal "Gutten Tag", @response.body + ensure + I18n.locale = old_locale end def test_default_locale_template_is_used_when_locale_is_missing + old_locale = I18n.locale I18n.locale = :dk get :hello_world assert_equal "Hello World", @response.body + ensure + I18n.locale = old_locale end -end
\ No newline at end of file +end diff --git a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb new file mode 100644 index 0000000000..22e603b881 --- /dev/null +++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb @@ -0,0 +1,50 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' + +class LogOnUnpermittedParamsTest < ActiveSupport::TestCase + def setup + ActionController::Parameters.action_on_unpermitted_parameters = :log + end + + def teardown + ActionController::Parameters.action_on_unpermitted_parameters = false + end + + test "logs on unexpected params" do + params = ActionController::Parameters.new({ + book: { pages: 65 }, + fishing: "Turnips" + }) + + assert_logged("Unpermitted parameters: fishing") do + params.permit(book: [:pages]) + end + end + + test "logs on unexpected nested params" do + params = ActionController::Parameters.new({ + book: { pages: 65, title: "Green Cats and where to find then." } + }) + + assert_logged("Unpermitted parameters: title") do + params.permit(book: [:pages]) + end + end + + private + + def assert_logged(message) + old_logger = ActionController::Base.logger + log = StringIO.new + ActionController::Base.logger = Logger.new(log) + + begin + yield + + log.rewind + assert_match message, log.read + ensure + ActionController::Base.logger = old_logger + end + end +end diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb index 6df849c4e2..91df527dec 100644 --- a/actionpack/test/controller/parameters/nested_parameters_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_test.rb @@ -2,6 +2,10 @@ require 'abstract_unit' require 'action_controller/metal/strong_parameters' class NestedParametersTest < ActiveSupport::TestCase + def assert_filtered_out(params, key) + assert !params.has_key?(key), "key #{key.inspect} has not been filtered out" + end + test "permitted nested parameters" do params = ActionController::Parameters.new({ book: { @@ -11,6 +15,8 @@ class NestedParametersTest < ActiveSupport::TestCase born: "1564-04-26" }, { name: "Christopher Marlowe" + }, { + name: %w(malicious injected names) }], details: { pages: 200, @@ -30,10 +36,12 @@ class NestedParametersTest < ActiveSupport::TestCase assert_equal "William Shakespeare", permitted[:book][:authors][0][:name] assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name] assert_equal 200, permitted[:book][:details][:pages] - assert_nil permitted[:book][:id] - assert_nil permitted[:book][:details][:genre] - assert_nil permitted[:book][:authors][0][:born] - assert_nil permitted[:magazine] + + assert_filtered_out permitted, :magazine + assert_filtered_out permitted[:book], :id + assert_filtered_out permitted[:book][:details], :genre + assert_filtered_out permitted[:book][:authors][0], :born + assert_filtered_out permitted[:book][:authors][2], :name end test "permitted nested parameters with a string or a symbol as a key" do @@ -63,25 +71,25 @@ class NestedParametersTest < ActiveSupport::TestCase test "nested arrays with strings" do params = ActionController::Parameters.new({ - :book => { - :genres => ["Tragedy"] + book: { + genres: ["Tragedy"] } }) - permitted = params.permit :book => :genres + permitted = params.permit book: {genres: []} assert_equal ["Tragedy"], permitted[:book][:genres] end test "permit may specify symbols or strings" do params = ActionController::Parameters.new({ - :book => { - :title => "Romeo and Juliet", - :author => "William Shakespeare" + book: { + title: "Romeo and Juliet", + author: "William Shakespeare" }, - :magazine => "Shakespeare Today" + magazine: "Shakespeare Today" }) - permitted = params.permit({:book => ["title", :author]}, "magazine") + permitted = params.permit({book: ["title", :author]}, "magazine") assert_equal "Romeo and Juliet", permitted[:book][:title] assert_equal "William Shakespeare", permitted[:book][:author] assert_equal "Shakespeare Today", permitted[:magazine] @@ -127,16 +135,38 @@ class NestedParametersTest < ActiveSupport::TestCase book: { authors_attributes: { :'0' => { name: 'William Shakespeare', age_of_death: '52' }, - :'-1' => { name: 'Unattributed Assistant' } + :'1' => { name: 'Unattributed Assistant' }, + :'2' => { name: %w(injected names)} } } }) permitted = params.permit book: { authors_attributes: [ :name ] } assert_not_nil permitted[:book][:authors_attributes]['0'] - assert_not_nil permitted[:book][:authors_attributes]['-1'] - assert_nil permitted[:book][:authors_attributes]['0'][:age_of_death] + assert_not_nil permitted[:book][:authors_attributes]['1'] + assert_empty permitted[:book][:authors_attributes]['2'] assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name] - assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-1'][:name] + assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][:name] + + assert_filtered_out permitted[:book][:authors_attributes]['0'], :age_of_death + end + + test "fields_for-style nested params with negative numbers" do + params = ActionController::Parameters.new({ + book: { + authors_attributes: { + :'-1' => { name: 'William Shakespeare', age_of_death: '52' }, + :'-2' => { name: 'Unattributed Assistant' } + } + } + }) + permitted = params.permit book: { authors_attributes: [:name] } + + assert_not_nil permitted[:book][:authors_attributes]['-1'] + assert_not_nil permitted[:book][:authors_attributes]['-2'] + assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['-1'][:name] + assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-2'][:name] + + assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death end end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 7cc71fe6dc..aadb142660 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -1,11 +1,133 @@ require 'abstract_unit' +require 'action_dispatch/http/upload' require 'action_controller/metal/strong_parameters' class ParametersPermitTest < ActiveSupport::TestCase + def assert_filtered_out(params, key) + assert !params.has_key?(key), "key #{key.inspect} has not been filtered out" + end + setup do @params = ActionController::Parameters.new({ person: { age: "32", name: { first: "David", last: "Heinemeier Hansson" } }}) + + @struct_fields = [] + %w(0 1 12).each do |number| + ['', 'i', 'f'].each do |suffix| + @struct_fields << "sf(#{number}#{suffix})" + end + end + end + + test 'if nothing is permitted, the hash becomes empty' do + params = ActionController::Parameters.new(id: '1234') + permitted = params.permit + assert permitted.permitted? + assert permitted.empty? + end + + test 'key: permitted scalar values' do + values = ['a', :a, nil] + values += [0, 1.0, 2**128, BigDecimal.new(1)] + values += [true, false] + values += [Date.today, Time.now, DateTime.now] + values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__)] + + values.each do |value| + params = ActionController::Parameters.new(id: value) + permitted = params.permit(:id) + assert_equal value, permitted[:id] + + @struct_fields.each do |sf| + params = ActionController::Parameters.new(sf => value) + permitted = params.permit(:sf) + assert_equal value, permitted[sf] + end + end + end + + test 'key: unknown keys are filtered out' do + params = ActionController::Parameters.new(id: '1234', injected: 'injected') + permitted = params.permit(:id) + assert_equal '1234', permitted[:id] + assert_filtered_out permitted, :injected + end + + test 'key: arrays are filtered out' do + [[], [1], ['1']].each do |array| + params = ActionController::Parameters.new(id: array) + permitted = params.permit(:id) + assert_filtered_out permitted, :id + + @struct_fields.each do |sf| + params = ActionController::Parameters.new(sf => array) + permitted = params.permit(:sf) + assert_filtered_out permitted, sf + end + end + end + + test 'key: hashes are filtered out' do + [{}, {foo: 1}, {foo: 'bar'}].each do |hash| + params = ActionController::Parameters.new(id: hash) + permitted = params.permit(:id) + assert_filtered_out permitted, :id + + @struct_fields.each do |sf| + params = ActionController::Parameters.new(sf => hash) + permitted = params.permit(:sf) + assert_filtered_out permitted, sf + end + end + end + + test 'key: non-permitted scalar values are filtered out' do + params = ActionController::Parameters.new(id: Object.new) + permitted = params.permit(:id) + assert_filtered_out permitted, :id + + @struct_fields.each do |sf| + params = ActionController::Parameters.new(sf => Object.new) + permitted = params.permit(:sf) + assert_filtered_out permitted, sf + end + end + + test 'key: it is not assigned if not present in params' do + params = ActionController::Parameters.new(name: 'Joe') + permitted = params.permit(:id) + assert !permitted.has_key?(:id) + end + + test 'key to empty array: empty arrays pass' do + params = ActionController::Parameters.new(id: []) + permitted = params.permit(id: []) + assert_equal [], permitted[:id] + end + + test 'key to empty array: arrays of permitted scalars pass' do + [['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array| + params = ActionController::Parameters.new(id: array) + permitted = params.permit(id: []) + assert_equal array, permitted[:id] + end + end + + test 'key to empty array: permitted scalar values do not pass' do + ['foo', 1].each do |permitted_scalar| + params = ActionController::Parameters.new(id: permitted_scalar) + permitted = params.permit(id: []) + assert_filtered_out permitted, :id + end + end + + test 'key to empty array: arrays of non-permitted scalar do not pass' do + [[Object.new], [[]], [[1]], [{}], [{id: '1'}]].each do |non_permitted_scalar| + params = ActionController::Parameters.new(id: non_permitted_scalar) + permitted = params.permit(id: []) + assert_filtered_out permitted, :id + end end test "fetch raises ParameterMissing exception" do @@ -73,10 +195,6 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "Jonas", @params[:person][:family][:brother] end - test "permitting parameters that are not there should not include the keys" do - assert !@params.permit(:person, :funky).has_key?(:funky) - end - test "permit state is kept on a dup" do @params.permit! assert_equal @params.permitted?, @params.dup.permitted? diff --git a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb new file mode 100644 index 0000000000..f9cc9f96f1 --- /dev/null +++ b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb @@ -0,0 +1,33 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' + +class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase + def setup + ActionController::Parameters.action_on_unpermitted_parameters = :raise + end + + def teardown + ActionController::Parameters.action_on_unpermitted_parameters = false + end + + test "raises on unexpected params" do + params = ActionController::Parameters.new({ + book: { pages: 65 }, + fishing: "Turnips" + }) + + assert_raises(ActionController::UnpermittedParameters) do + params.permit(book: [:pages]) + end + end + + test "raises on unexpected nested params" do + params = ActionController::Parameters.new({ + book: { pages: 65, title: "Green Cats and where to find then." } + }) + + assert_raises(ActionController::UnpermittedParameters) do + params.permit(book: [:pages]) + end + end +end diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb new file mode 100644 index 0000000000..ff5d7fd3bd --- /dev/null +++ b/actionpack/test/controller/record_identifier_test.rb @@ -0,0 +1,34 @@ +require 'abstract_unit' +require 'controller/fake_models' + +class ControllerRecordIdentifierTest < ActiveSupport::TestCase + include ActionController::RecordIdentifier + + def setup + @record = Comment.new + end + + def test_dom_id_deprecation + assert_deprecated(/dom_id method will no longer be included by default in controllers/) do + dom_id(@record) + end + end + + def test_dom_class_deprecation + assert_deprecated(/dom_class method will no longer be included by default in controllers/) do + dom_class(@record) + end + end + + def test_dom_id_from_module_deprecation + assert_deprecated(/Calling ActionController::RecordIdentifier.dom_id is deprecated/) do + ActionController::RecordIdentifier.dom_id(@record) + end + end + + def test_dom_class_from_module_deprecation + assert_deprecated(/Calling ActionController::RecordIdentifier.dom_class is deprecated/) do + ActionController::RecordIdentifier.dom_class(@record) + end + end +end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 523a8d0572..c272e785c2 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -66,6 +66,19 @@ class RequestForgeryProtectionControllerUsingException < ActionController::Base protect_from_forgery :only => %w(index meta), :with => :exception end +class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base + protect_from_forgery :with => :null_session + + def signed + cookies.signed[:foo] = 'bar' + render :nothing => true + end + + def encrypted + cookies.encrypted[:foo] = 'bar' + render :nothing => true + end +end class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession self.allow_forgery_protection = false @@ -170,6 +183,10 @@ module RequestForgeryProtectionTests assert_not_blocked { get :index } end + def test_should_allow_head + assert_not_blocked { head :index } + end + def test_should_allow_post_without_token_on_unsafe_action assert_not_blocked { post :unsafe } end @@ -283,6 +300,28 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController end end +class NullSessionDummyKeyGenerator + def generate_key(secret) + '03312270731a2ed0d11ed091c2338a06' + end +end + +class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase + def setup + @request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new + end + + test 'should allow to set signed cookies' do + post :signed + assert_response :ok + end + + test 'should allow to set encrypted cookies' do + post :encrypted + assert_response :ok + end +end + class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase include RequestForgeryProtectionTests def assert_blocked diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 305659b219..9aea7e860a 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1,6 +1,7 @@ require 'abstract_unit' require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/with_options' +require 'active_support/core_ext/array/extract_options' class ResourcesTest < ActionController::TestCase def test_default_restful_routes diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 0480295056..19d5652d81 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -129,19 +129,6 @@ class WebServiceTest < ActionDispatch::IntegrationTest $stderr = STDERR end - def test_register_and_use_yaml - with_test_route_set do - with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do - post "/", {"entry" => "loaded from yaml"}.to_yaml, - {'CONTENT_TYPE' => 'application/x-yaml'} - - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'loaded from yaml', @controller.params["entry"] - end - end - end - def test_register_and_use_xml_simple with_test_route_set do with_params_parsers Mime::XML => Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } do diff --git a/actionpack/test/dispatch/best_standards_support_test.rb b/actionpack/test/dispatch/best_standards_support_test.rb deleted file mode 100644 index 551bb9621a..0000000000 --- a/actionpack/test/dispatch/best_standards_support_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'abstract_unit' - -class BestStandardsSupportTest < ActiveSupport::TestCase - def test_with_best_standards_support - _, headers, _ = app(true, {}).call({}) - assert_equal "IE=Edge,chrome=1", headers["X-UA-Compatible"] - end - - def test_with_builtin_best_standards_support - _, headers, _ = app(:builtin, {}).call({}) - assert_equal "IE=Edge", headers["X-UA-Compatible"] - end - - def test_without_best_standards_support - _, headers, _ = app(false, {}).call({}) - assert_equal nil, headers["X-UA-Compatible"] - end - - def test_appends_to_app_headers_without_duplication_after_multiple_requests - app_headers = { "X-UA-Compatible" => "requiresActiveX=true" } - _, headers, _ = app(true, app_headers).call({}) - _, headers, _ = app(true, app_headers).call({}) - - expects = "requiresActiveX=true,IE=Edge,chrome=1" - assert_equal expects, headers["X-UA-Compatible"] - end - - private - - def app(type, headers) - app = proc { [200, headers, "response"] } - ActionDispatch::BestStandardsSupport.new(app, type) - end - -end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index ffa91d63c4..66d2cade22 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -160,6 +160,36 @@ class CookiesTest < ActionController::TestCase @request.host = "www.nextangle.com" end + def test_fetch + x = Object.new + assert_not request.cookie_jar.key?('zzzzzz') + assert_equal x, request.cookie_jar.fetch('zzzzzz', x) + assert_not request.cookie_jar.key?('zzzzzz') + end + + def test_fetch_exists + x = Object.new + request.cookie_jar['foo'] = 'bar' + assert_equal 'bar', request.cookie_jar.fetch('foo', x) + end + + def test_fetch_block + x = Object.new + assert_not request.cookie_jar.key?('zzzzzz') + assert_equal x, request.cookie_jar.fetch('zzzzzz') { x } + end + + def test_key_is_to_s + request.cookie_jar['foo'] = 'bar' + assert_equal 'bar', request.cookie_jar.fetch(:foo) + end + + def test_fetch_type_error + assert_raises(KeyError) do + request.cookie_jar.fetch(:omglolwut) + end + end + def test_each request.cookie_jar['foo'] = :bar list = [] @@ -211,13 +241,13 @@ class CookiesTest < ActionController::TestCase def test_setting_cookie_for_fourteen_days get :authenticate_for_fourteen_days - assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT" + assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" assert_equal({"user_name" => "david"}, @response.cookies) end def test_setting_cookie_for_fourteen_days_with_symbols get :authenticate_for_fourteen_days_with_symbols - assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT" + assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" assert_equal({"user_name" => "david"}, @response.cookies) end @@ -250,7 +280,7 @@ class CookiesTest < ActionController::TestCase def test_multiple_cookies get :set_multiple_cookies assert_equal 2, @response.cookies.size - assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT\nlogin=XJ-122; path=/" + assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000\nlogin=XJ-122; path=/" assert_equal({"login" => "XJ-122", "user_name" => "david"}, @response.cookies) end @@ -261,14 +291,14 @@ class CookiesTest < ActionController::TestCase def test_expiring_cookie request.cookies[:user_name] = 'Joe' get :logout - assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" assert_equal({"user_name" => nil}, @response.cookies) end def test_delete_cookie_with_path request.cookies[:user_name] = 'Joe' get :delete_cookie_with_path - assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; path=/beaten; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_delete_unexisting_cookie @@ -330,7 +360,7 @@ class CookiesTest < ActionController::TestCase def test_delete_and_set_cookie request.cookies[:user_name] = 'Joe' get :delete_and_set_cookie - assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT" + assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" assert_equal({"user_name" => "david"}, @response.cookies) end @@ -435,7 +465,7 @@ class CookiesTest < ActionController::TestCase request.cookies[:user_name] = 'Joe' get :delete_cookie_with_domain assert_response :success - assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; domain=.nextangle.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_cookie_with_all_domain_option_and_tld_length @@ -462,7 +492,7 @@ class CookiesTest < ActionController::TestCase request.cookies[:user_name] = 'Joe' get :delete_cookie_with_domain_and_tld assert_response :success - assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; domain=.nextangle.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_cookie_with_several_preset_domains_using_one_of_these_domains @@ -491,7 +521,7 @@ class CookiesTest < ActionController::TestCase request.cookies[:user_name] = 'Joe' get :delete_cookie_with_domains assert_response :success - assert_cookie_header "user_name=; domain=example2.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; domain=example2.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_deletings_cookie_with_several_preset_domains_using_other_domain @@ -499,7 +529,7 @@ class CookiesTest < ActionController::TestCase request.cookies[:user_name] = 'Joe' get :delete_cookie_with_domains assert_response :success - assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_cookies_hash_is_indifferent_access diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index cbb086f605..91810864d5 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -590,6 +590,10 @@ class RequestTest < ActiveSupport::TestCase request.expects(:parameters).at_least_once.returns({}) assert_equal [Mime::HTML], request.formats + request = stub_request 'HTTP_ACCEPT' => '' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [Mime::HTML], request.formats + request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 185a9e9b18..74f5253c11 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -35,7 +35,7 @@ class ResponseTest < ActiveSupport::TestCase end def test_response_body_encoding - body = ["hello".encode('utf-8')] + body = ["hello".encode(Encoding::UTF_8)] response = ActionDispatch::Response.new 200, {}, body assert_equal Encoding::UTF_8, response.body.encoding end @@ -129,7 +129,7 @@ class ResponseTest < ActiveSupport::TestCase @response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5)) status, headers, body = @response.to_a - assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT", headers["Set-Cookie"] + assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000", headers["Set-Cookie"] assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies) @response.delete_cookie("login") @@ -182,7 +182,8 @@ class ResponseTest < ActiveSupport::TestCase ActionDispatch::Response.default_headers = { 'X-Frame-Options' => 'DENY', 'X-Content-Type-Options' => 'nosniff', - 'X-XSS-Protection' => '1;' + 'X-XSS-Protection' => '1;', + 'X-UA-Compatible' => 'chrome=1' } resp = ActionDispatch::Response.new.tap { |response| response.body = 'Hello' @@ -192,6 +193,7 @@ class ResponseTest < ActiveSupport::TestCase assert_equal('DENY', resp.headers['X-Frame-Options']) assert_equal('nosniff', resp.headers['X-Content-Type-Options']) assert_equal('1;', resp.headers['X-XSS-Protection']) + assert_equal('chrome=1', resp.headers['X-UA-Compatible']) ensure ActionDispatch::Response.default_headers = nil end diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index c7dcb5a683..55221f87c4 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -21,6 +21,14 @@ module ActionDispatch inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n") end + def test_json_regexp_converter + @set.draw do + get '/cart', :to => 'cart#show' + end + route = ActionDispatch::Routing::RouteWrapper.new(@set.routes.first) + assert_equal "^\\/cart(?:\\.([^\\/.?]+))?$", route.json_regexp + end + def test_displaying_routes_for_engines engine = Class.new(Rails::Engine) do def self.inspect diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index da7474e73c..143733254b 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1031,6 +1031,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'users/home#index', @response.body end + def test_namespace_containing_numbers + draw do + namespace :v2 do + resources :subscriptions + end + end + + get '/v2/subscriptions' + assert_equal 'v2/subscriptions#index', @response.body + assert_equal '/v2/subscriptions', v2_subscriptions_path + end + def test_articles_with_id draw do controller :articles do @@ -2678,6 +2690,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '0c0c0b68-d24b-11e1-a861-001ff3fffe6f', @request.params[:download] end + def test_action_from_path_is_not_frozen + draw do + get 'search' => 'search' + end + + get '/search' + assert !@request.params[:action].frozen? + end + private def draw(&block) @@ -2824,21 +2845,52 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest end end - DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new - DefaultScopeRoutes.draw do - namespace :admin do - resources :storage_files, :controller => "StorageFiles" - end + def draw(&block) + @app = ActionDispatch::Routing::RouteSet.new + @app.draw(&block) end - def app - DefaultScopeRoutes - end + def test_valid_controller_options_inside_namespace + draw do + namespace :admin do + resources :storage_files, :controller => "storage_files" + end + end - def test_controller_options get '/admin/storage_files' assert_equal "admin/storage_files#index", @response.body end + + def test_resources_with_valid_namespaced_controller_option + draw do + resources :storage_files, :controller => 'admin/storage_files' + end + + get 'storage_files' + assert_equal "admin/storage_files#index", @response.body + end + + def test_warn_with_ruby_constant_syntax_controller_option + e = assert_raise(ArgumentError) do + draw do + namespace :admin do + resources :storage_files, :controller => "StorageFiles" + end + end + end + + assert_match "'admin/StorageFiles' is not a supported controller name", e.message + end + + def test_warn_with_ruby_constant_syntax_namespaced_controller_option + e = assert_raise(ArgumentError) do + draw do + resources :storage_files, :controller => 'Admin::StorageFiles' + end + end + + assert_match "'Admin::StorageFiles' is not a supported controller name", e.message + end end class TestDefaultScope < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 1677dee524..d8bf22dec8 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -276,7 +276,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest # First request accesses the session time = Time.local(2008, 4, 24) Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") cookies[SessionKey] = SignedBar @@ -290,7 +290,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest # Second request does not access the session time = Time.local(2008, 4, 25) Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") get '/no_session_access' assert_response :success diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 904398f563..d30461a623 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -2,8 +2,8 @@ require 'abstract_unit' require 'stringio' class ActionController::TestSessionTest < ActiveSupport::TestCase - def test_ctor_allows_setting - session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) + def test_initialize_with_values + session = ActionController::TestSession.new(one: 'one', two: 'two') assert_equal('one', session[:one]) assert_equal('two', session[:two]) end @@ -23,15 +23,21 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase end def test_calling_update_with_params_passes_to_attributes - session = ActionController::TestSession.new() + session = ActionController::TestSession.new session.update('key' => 'value') assert_equal('value', session[:key]) end - def test_clear_emptys_session - session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) + def test_clear_empties_session + session = ActionController::TestSession.new(one: 'one', two: 'two') session.clear assert_nil(session[:one]) assert_nil(session[:two]) end + + def test_keys_and_values + session = ActionController::TestSession.new(one: '1', two: '2') + assert_equal %w(one two), session.keys + assert_equal %w(1 2), session.values + end end diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 6047631ba3..3db862c810 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -18,7 +18,7 @@ class TestRequestTest < ActiveSupport::TestCase assert_equal "0.0.0.0", env.delete("REMOTE_ADDR") assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT") - assert_equal [1, 1], env.delete("rack.version") + assert_equal [1, 2], env.delete("rack.version") assert_equal "", env.delete("rack.input").string assert_kind_of StringIO, env.delete("rack.errors") assert_equal true, env.delete("rack.multithread") @@ -40,10 +40,10 @@ class TestRequestTest < ActiveSupport::TestCase req.cookie_jar["login"] = "XJ-122" assert_cookies({"user_name" => "david", "login" => "XJ-122"}, req.cookie_jar) - assert_nothing_raised do + assert_nothing_raised do req.cookie_jar["login"] = nil assert_cookies({"user_name" => "david", "login" => nil}, req.cookie_jar) - end + end req.cookie_jar.delete(:login) assert_cookies({"user_name" => "david"}, req.cookie_jar) diff --git a/actionpack/test/fixtures/developer.rb b/actionpack/test/fixtures/developer.rb index dd14548fac..4941463015 100644 --- a/actionpack/test/fixtures/developer.rb +++ b/actionpack/test/fixtures/developer.rb @@ -2,6 +2,7 @@ class Developer < ActiveRecord::Base has_and_belongs_to_many :projects has_many :replies has_many :topics, :through => :replies + accepts_nested_attributes_for :projects end class DeVeLoPeR < ActiveRecord::Base diff --git a/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb b/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb new file mode 100644 index 0000000000..1cc8d41475 --- /dev/null +++ b/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb @@ -0,0 +1 @@ +Hello <%= name %> diff --git a/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb b/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb new file mode 100644 index 0000000000..1461b95186 --- /dev/null +++ b/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb @@ -0,0 +1 @@ +<%= render partial: 'test/_directory/partial_with_locales', locals: {'name' => 'Jane'} %> diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 82c9d383ac..11614a45dc 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -443,7 +443,8 @@ class AssetTagHelperTest < ActionView::TestCase [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix| assert_equal 'Rails', image_alt("#{prefix}rails.png") assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png") - assert_equal 'Avatar-0000', image_alt("#{prefix}avatar-0000.png") + assert_equal 'Long file name with hyphens', image_alt("#{prefix}long-file-name-with-hyphens.png") + assert_equal 'Long file name with underscores', image_alt("#{prefix}long_file_name_with_underscores.png") end end diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb index 495a9d3f9d..21fca35185 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -117,7 +117,7 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase I18n.expects(:translate).with(('datetime.prompts.' + key.to_s).to_sym, :locale => 'en').returns prompt end - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(year month day) datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true) end @@ -129,15 +129,20 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase end def test_date_or_time_select_given_no_order_options_translates_order - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(year month day) datetime_select('post', 'updated_at', :locale => 'en') end def test_date_or_time_select_given_invalid_order - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:invalid, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(invalid month day) assert_raise StandardError do datetime_select('post', 'updated_at', :locale => 'en') end end + + def test_date_or_time_select_given_symbol_keys + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] + datetime_select('post', 'updated_at', :locale => 'en') + end end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index d622392caf..f11adefad8 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1529,7 +1529,24 @@ class DateHelperTest < ActionView::TestCase 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 def test_date_select_without_day @@ -2005,7 +2022,26 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 20}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, time_select("post", "written_on", :selected => Time.local(2004, 6, 15, 12, 20, 30)) + assert_dom_equal expected, time_select("post", "written_on", selected: Time.local(2004, 6, 15, 12, 20, 30)) + end + + def test_time_select_with_selected_nil + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="1" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="1" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="1" />\n} + + expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}">#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}">#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", discard_year: true, discard_month: true, discard_day: true, selected: nil) end def test_time_select_without_date_hidden_fields @@ -2234,6 +2270,33 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at", :selected => Time.local(2004, 3, 10, 12, 30)) end + def test_datetime_select_with_selected_nil + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 16, 35) + + expected = '<input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="1"/>' + "\n" + + expected << %{<select id="post_updated_at_2i" name="post[updated_at(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">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_updated_at_3i" name="post[updated_at(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">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" + + expected << " — " + + expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</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} + expected << "</select>\n" + expected << " : " + expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</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<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} + expected << "</select>\n" + + 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) { diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index f9890a2eef..268bab6ad2 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -15,26 +15,26 @@ class FormHelperTest < ActionView::TestCase # Create "label" locale for testing I18n label helpers I18n.backend.store_translations 'label', { - :activemodel => { - :attributes => { - :post => { - :cost => "Total cost" + activemodel: { + attributes: { + post: { + cost: "Total cost" } } }, - :helpers => { - :label => { - :post => { - :body => "Write entire text here", - :color => { - :red => "Rojo" + helpers: { + label: { + post: { + body: "Write entire text here", + color: { + red: "Rojo" }, - :comments => { - :body => "Write body here" + comments: { + body: "Write body here" } }, - :tag => { - :value => "Tag" + tag: { + value: "Tag" } } } @@ -42,13 +42,13 @@ class FormHelperTest < ActionView::TestCase # Create "submit" locale for testing I18n submit helpers I18n.backend.store_translations 'submit', { - :helpers => { - :submit => { - :create => 'Create %{model}', - :update => 'Confirm %{model} changes', - :submit => 'Save changes', - :another_post => { - :update => 'Update your %{model}' + helpers: { + submit: { + create: 'Create %{model}', + update: 'Confirm %{model} changes', + submit: 'Save changes', + another_post: { + update: 'Update your %{model}' } } } @@ -57,11 +57,11 @@ class FormHelperTest < ActionView::TestCase @post = Post.new @comment = Comment.new def @post.errors() - Class.new{ + Class.new { def [](field); field == "author_name" ? ["can't be empty"] : [] end def empty?() false end def count() 1 end - def full_messages() [ "Author name can't be empty" ] end + def full_messages() ["Author name can't be empty"] end }.new end def @post.to_key; [123]; end @@ -96,8 +96,8 @@ class FormHelperTest < ActionView::TestCase end end - get "/foo", :to => "controller#action" - root :to => "main#index" + get "/foo", to: "controller#action" + root to: "main#index" end def _routes @@ -108,9 +108,11 @@ class FormHelperTest < ActionView::TestCase def url_for(object) @url_for_options = object + if object.is_a?(Hash) && object[:use_route].blank? && object[:controller].blank? - object.merge!(:controller => "main", :action => "index") + object.merge!(controller: "main", action: "index") end + super end @@ -124,10 +126,13 @@ class FormHelperTest < ActionView::TestCase def test_label assert_dom_equal('<label for="post_title">Title</label>', label("post", "title")) - assert_dom_equal('<label for="post_title">The title goes here</label>', label("post", "title", "The title goes here")) + assert_dom_equal( + '<label for="post_title">The title goes here</label>', + label("post", "title", "The title goes here") + ) assert_dom_equal( '<label class="title_label" for="post_title">Title</label>', - label("post", "title", nil, :class => 'title_label') + label("post", "title", nil, class: 'title_label') ) assert_dom_equal('<label for="post_secret">Secret?</label>', label("post", "secret?")) end @@ -160,28 +165,31 @@ class FormHelperTest < ActionView::TestCase def test_label_with_locales_and_options old_locale, I18n.locale = I18n.locale, :label - assert_dom_equal('<label for="post_body" class="post_body">Write entire text here</label>', label(:post, :body, :class => 'post_body')) + assert_dom_equal( + '<label for="post_body" class="post_body">Write entire text here</label>', + label(:post, :body, class: "post_body") + ) ensure I18n.locale = old_locale end def test_label_with_locales_and_value old_locale, I18n.locale = I18n.locale, :label - assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, :value => "red")) + assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, value: "red")) ensure I18n.locale = old_locale end def test_label_with_locales_and_nested_attributes old_locale, I18n.locale = I18n.locale, :label - form_for(@post, :html => { :id => 'create-post' }) do |f| + form_for(@post, html: { id: 'create-post' }) do |f| f.fields_for(:comments) do |cf| concat cf.label(:body) end end - expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do - "<label for=\"post_comments_attributes_0_body\">Write body here</label>" + expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do + '<label for="post_comments_attributes_0_body">Write body here</label>' end assert_dom_equal expected, output_buffer @@ -191,14 +199,14 @@ class FormHelperTest < ActionView::TestCase def test_label_with_locales_fallback_and_nested_attributes old_locale, I18n.locale = I18n.locale, :label - form_for(@post, :html => { :id => 'create-post' }) do |f| + form_for(@post, html: { id: 'create-post' }) do |f| f.fields_for(:tags) do |cf| concat cf.label(:value) end end - expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do - "<label for=\"post_tags_attributes_0_value\">Tag</label>" + expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do + '<label for="post_tags_attributes_0_value">Tag</label>' end assert_dom_equal expected, output_buffer @@ -207,7 +215,7 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_for_attribute_as_symbol - assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for")) + assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, for: "my_for")) end def test_label_with_for_attribute_as_string @@ -215,61 +223,93 @@ class FormHelperTest < ActionView::TestCase end def test_label_does_not_generate_for_attribute_when_given_nil - assert_dom_equal('<label>Title</label>', label(:post, :title, :for => nil)) + assert_dom_equal('<label>Title</label>', label(:post, :title, for: nil)) end def test_label_with_id_attribute_as_symbol - assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, :id => "my_id")) + assert_dom_equal( + '<label for="post_title" id="my_id">Title</label>', + label(:post, :title, nil, id: "my_id") + ) end def test_label_with_id_attribute_as_string - assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, "id" => "my_id")) + assert_dom_equal( + '<label for="post_title" id="my_id">Title</label>', + label(:post, :title, nil, "id" => "my_id") + ) end def test_label_with_for_and_id_attributes_as_symbol - assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, :for => "my_for", :id => "my_id")) + assert_dom_equal( + '<label for="my_for" id="my_id">Title</label>', + label(:post, :title, nil, for: "my_for", id: "my_id") + ) end def test_label_with_for_and_id_attributes_as_string - assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, "for" => "my_for", "id" => "my_id")) + assert_dom_equal( + '<label for="my_for" id="my_id">Title</label>', + label(:post, :title, nil, "for" => "my_for", "id" => "my_id") + ) end def test_label_for_radio_buttons_with_value - assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great_title")) - assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great title")) + assert_dom_equal( + '<label for="post_title_great_title">The title goes here</label>', + label("post", "title", "The title goes here", value: "great_title") + ) + assert_dom_equal( + '<label for="post_title_great_title">The title goes here</label>', + label("post", "title", "The title goes here", value: "great title") + ) end def test_label_with_block - assert_dom_equal('<label for="post_title">The title, please:</label>', label(:post, :title) { "The title, please:" }) + assert_dom_equal( + '<label for="post_title">The title, please:</label>', + label(:post, :title) { "The title, please:" } + ) end def test_label_with_block_and_options - assert_dom_equal('<label for="my_for">The title, please:</label>', label(:post, :title, "for" => "my_for") { "The title, please:" }) + assert_dom_equal( + '<label for="my_for">The title, please:</label>', + label(:post, :title, "for" => "my_for") { "The title, please:" } + ) end def test_label_with_block_in_erb - assert_equal "<label for=\"post_message\">\n Message\n <input id=\"post_message\" name=\"post[message]\" type=\"text\" />\n</label>", view.render("test/label_with_block") + assert_equal( + %{<label for="post_message">\n Message\n <input id="post_message" name="post[message]" type="text" />\n</label>}, + view.render("test/label_with_block") + ) end def test_text_field assert_dom_equal( - '<input id="post_title" name="post[title]" type="text" value="Hello World" />', text_field("post", "title") + '<input id="post_title" name="post[title]" type="text" value="Hello World" />', + text_field("post", "title") ) assert_dom_equal( - '<input id="post_title" name="post[title]" type="password" />', password_field("post", "title") + '<input id="post_title" name="post[title]" type="password" />', + password_field("post", "title") ) assert_dom_equal( - '<input id="post_title" name="post[title]" type="password" value="Hello World" />', password_field("post", "title", :value => @post.title) + '<input id="post_title" name="post[title]" type="password" value="Hello World" />', + password_field("post", "title", value: @post.title) ) assert_dom_equal( - '<input id="person_name" name="person[name]" type="password" />', password_field("person", "name") + '<input id="person_name" name="person[name]" type="password" />', + password_field("person", "name") ) end def test_text_field_with_escapes @post.title = "<b>Hello World</b>" assert_dom_equal( - '<input id="post_title" name="post[title]" type="text" value="<b>Hello World</b>" />', text_field("post", "title") + '<input id="post_title" name="post[title]" type="text" value="<b>Hello World</b>" />', + text_field("post", "title") ) end @@ -284,29 +324,29 @@ class FormHelperTest < ActionView::TestCase def test_text_field_with_options expected = '<input id="post_title" name="post[title]" size="35" type="text" value="Hello World" />' assert_dom_equal expected, text_field("post", "title", "size" => 35) - assert_dom_equal expected, text_field("post", "title", :size => 35) + assert_dom_equal expected, text_field("post", "title", size: 35) end def test_text_field_assuming_size expected = '<input id="post_title" maxlength="35" name="post[title]" size="35" type="text" value="Hello World" />' assert_dom_equal expected, text_field("post", "title", "maxlength" => 35) - assert_dom_equal expected, text_field("post", "title", :maxlength => 35) + assert_dom_equal expected, text_field("post", "title", maxlength: 35) end def test_text_field_removing_size expected = '<input id="post_title" maxlength="35" name="post[title]" type="text" value="Hello World" />' assert_dom_equal expected, text_field("post", "title", "maxlength" => 35, "size" => nil) - assert_dom_equal expected, text_field("post", "title", :maxlength => 35, :size => nil) + assert_dom_equal expected, text_field("post", "title", maxlength: 35, size: nil) end def test_text_field_with_nil_value expected = '<input id="post_title" name="post[title]" type="text" />' - assert_dom_equal expected, text_field("post", "title", :value => nil) + assert_dom_equal expected, text_field("post", "title", value: nil) end def test_text_field_with_nil_name expected = '<input id="post_title" type="text" value="Hello World" />' - assert_dom_equal expected, text_field("post", "title", :name => nil) + assert_dom_equal expected, text_field("post", "title", name: nil) end def test_text_field_doesnt_change_param_values @@ -322,31 +362,41 @@ class FormHelperTest < ActionView::TestCase end def test_hidden_field - assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />', + assert_dom_equal( + '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />', hidden_field("post", "title") - assert_dom_equal '<input id="post_secret" name="post[secret]" type="hidden" value="1" />', - hidden_field("post", "secret?") + ) + assert_dom_equal( + '<input id="post_secret" name="post[secret]" type="hidden" value="1" />', + hidden_field("post", "secret?") + ) end def test_hidden_field_with_escapes @post.title = "<b>Hello World</b>" - assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="<b>Hello World</b>" />', + assert_dom_equal( + '<input id="post_title" name="post[title]" type="hidden" value="<b>Hello World</b>" />', hidden_field("post", "title") + ) end def test_hidden_field_with_nil_value expected = '<input id="post_title" name="post[title]" type="hidden" />' - assert_dom_equal expected, hidden_field("post", "title", :value => nil) + assert_dom_equal expected, hidden_field("post", "title", value: nil) end def test_hidden_field_with_options - assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Something Else" />', - hidden_field("post", "title", :value => "Something Else") + assert_dom_equal( + '<input id="post_title" name="post[title]" type="hidden" value="Something Else" />', + hidden_field("post", "title", value: "Something Else") + ) end def test_text_field_with_custom_type - assert_dom_equal '<input id="user_email" name="user[email]" type="email" />', - text_field("user", "email", :type => "email") + assert_dom_equal( + '<input id="user_email" name="user[email]" type="email" />', + text_field("user", "email", type: "email") + ) end def test_check_box_is_html_safe @@ -371,7 +421,7 @@ class FormHelperTest < ActionView::TestCase def test_check_box_checked_if_option_checked_is_present assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', - check_box("post", "secret" ,{"checked"=>"checked"}) + check_box("post", "secret", "checked"=>"checked") ) end @@ -410,7 +460,10 @@ class FormHelperTest < ActionView::TestCase def test_check_box_with_include_hidden_false @post.secret = false - assert_dom_equal('<input id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret", :include_hidden => false)) + assert_dom_equal( + '<input id="post_secret" name="post[secret]" type="checkbox" value="1" />', + check_box("post", "secret", include_hidden: false) + ) end def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_string @@ -517,11 +570,11 @@ class FormHelperTest < ActionView::TestCase @post.comment_ids = [2,3] assert_dom_equal( '<input name="post[comment_ids][]" type="hidden" value="0" /><input id="post_comment_ids_1" name="post[comment_ids][]" type="checkbox" value="1" />', - check_box("post", "comment_ids", { :multiple => true }, 1) + check_box("post", "comment_ids", { multiple: true }, 1) ) assert_dom_equal( '<input name="post[comment_ids][]" type="hidden" value="0" /><input checked="checked" id="post_comment_ids_3" name="post[comment_ids][]" type="checkbox" value="3" />', - check_box("post", "comment_ids", { :multiple => true }, 3) + check_box("post", "comment_ids", { multiple: true }, 3) ) end @@ -529,11 +582,11 @@ class FormHelperTest < ActionView::TestCase @post.comment_ids = [2,3] assert_dom_equal( '<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" />', - check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1) + check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) ) assert_dom_equal( '<input name="post[bar][comment_ids][]" type="hidden" value="0" /><input checked="checked" id="post_bar_comment_ids_3" name="post[bar][comment_ids][]" type="checkbox" value="3" />', - check_box("post", "comment_ids", { :multiple => true, :index => "bar" }, 3) + check_box("post", "comment_ids", { multiple: true, index: "bar" }, 3) ) end @@ -541,14 +594,14 @@ class FormHelperTest < ActionView::TestCase def test_checkbox_disabled_disables_hidden_field assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" disabled="disabled"/><input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" />', - check_box("post", "secret", { :disabled => true }) + check_box("post", "secret", { disabled: true }) ) end def test_checkbox_form_html5_attribute assert_dom_equal( '<input form="new_form" name="post[secret]" type="hidden" value="0" /><input checked="checked" form="new_form" id="post_secret" name="post[secret]" type="checkbox" value="1" />', - check_box("post", "secret", :form => "new_form") + check_box("post", "secret", form: "new_form") ) end @@ -577,7 +630,7 @@ class FormHelperTest < ActionView::TestCase def test_radio_button_respects_passed_in_id assert_dom_equal('<input checked="checked" id="foo" name="post[secret]" type="radio" value="1" />', - radio_button("post", "secret", "1", :id=>"foo") + radio_button("post", "secret", "1", id: "foo") ) end @@ -599,7 +652,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_with_escapes - @post.body = "Back to <i>the</i> hill and over it again!" + @post.body = "Back to <i>the</i> hill and over it again!" assert_dom_equal( %{<textarea id="post_body" name="post[body]">\nBack to <i>the</i> hill and over it again!</textarea>}, text_area("post", "body") @@ -609,12 +662,12 @@ class FormHelperTest < ActionView::TestCase def test_text_area_with_alternate_value assert_dom_equal( %{<textarea id="post_body" name="post[body]">\nTesting alternate values.</textarea>}, - text_area("post", "body", :value => 'Testing alternate values.') + text_area("post", "body", value: "Testing alternate values.") ) end def test_text_area_with_html_entities - @post.body = "The HTML Entity for & is &" + @post.body = "The HTML Entity for & is &" assert_dom_equal( %{<textarea id="post_body" name="post[body]">\nThe HTML Entity for & is &amp;</textarea>}, text_area("post", "body") @@ -624,7 +677,7 @@ class FormHelperTest < ActionView::TestCase def test_text_area_with_size_option assert_dom_equal( %{<textarea cols="183" id="post_body" name="post[body]" rows="820">\nBack to the hill and over it again!</textarea>}, - text_area("post", "body", :size => "183x820") + text_area("post", "body", size: "183x820") ) end @@ -666,7 +719,7 @@ class FormHelperTest < ActionView::TestCase min_value = DateTime.new(2000, 6, 15) max_value = DateTime.new(2010, 8, 15) step = 2 - assert_dom_equal(expected, date_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + assert_dom_equal(expected, date_field("post", "written_on", min: min_value, max: max_value, step: step)) end def test_date_field_with_timewithzone_value @@ -701,7 +754,7 @@ class FormHelperTest < ActionView::TestCase min_value = DateTime.new(2000, 6, 15, 20, 45, 30) max_value = DateTime.new(2010, 8, 15, 10, 25, 00) step = 60 - assert_dom_equal(expected, time_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + assert_dom_equal(expected, time_field("post", "written_on", min: min_value, max: max_value, step: step)) end def test_time_field_with_timewithzone_value @@ -736,7 +789,7 @@ class FormHelperTest < ActionView::TestCase min_value = DateTime.new(2000, 6, 15, 20, 45, 30) max_value = DateTime.new(2010, 8, 15, 10, 25, 00) step = 60 - assert_dom_equal(expected, datetime_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value, step: step)) end def test_datetime_field_with_timewithzone_value @@ -771,7 +824,7 @@ class FormHelperTest < ActionView::TestCase min_value = DateTime.new(2000, 6, 15, 20, 45, 30) max_value = DateTime.new(2010, 8, 15, 10, 25, 00) step = 60 - assert_dom_equal(expected, datetime_local_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + assert_dom_equal(expected, datetime_local_field("post", "written_on", min: min_value, max: max_value, step: step)) end def test_datetime_local_field_with_timewithzone_value @@ -812,7 +865,7 @@ class FormHelperTest < ActionView::TestCase min_value = DateTime.new(2000, 2, 13) max_value = DateTime.new(2010, 12, 23) step = 2 - assert_dom_equal(expected, month_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + assert_dom_equal(expected, month_field("post", "written_on", min: min_value, max: max_value, step: step)) end def test_month_field_with_timewithzone_value @@ -847,7 +900,7 @@ class FormHelperTest < ActionView::TestCase min_value = DateTime.new(2000, 2, 13) max_value = DateTime.new(2010, 12, 23) step = 2 - assert_dom_equal(expected, week_field("post", "written_on", :min => min_value, :max => max_value, :step => step)) + assert_dom_equal(expected, week_field("post", "written_on", min: min_value, max: max_value, step: step)) end def test_week_field_with_timewithzone_value @@ -871,21 +924,22 @@ class FormHelperTest < ActionView::TestCase def test_number_field expected = %{<input name="order[quantity]" max="9" id="order_quantity" type="number" min="1" />} - assert_dom_equal(expected, number_field("order", "quantity", :in => 1...10)) + assert_dom_equal(expected, number_field("order", "quantity", in: 1...10)) expected = %{<input name="order[quantity]" size="30" max="9" id="order_quantity" type="number" min="1" />} - assert_dom_equal(expected, number_field("order", "quantity", :size => 30, :in => 1...10)) + assert_dom_equal(expected, number_field("order", "quantity", size: 30, in: 1...10)) end def test_range_input expected = %{<input name="hifi[volume]" step="0.1" max="11" id="hifi_volume" type="range" min="0" />} - assert_dom_equal(expected, range_field("hifi", "volume", :in => 0..11, :step => 0.1)) + assert_dom_equal(expected, range_field("hifi", "volume", in: 0..11, step: 0.1)) expected = %{<input name="hifi[volume]" step="0.1" size="30" max="11" id="hifi_volume" type="range" min="0" />} - assert_dom_equal(expected, range_field("hifi", "volume", :size => 30, :in => 0..11, :step => 0.1)) + assert_dom_equal(expected, range_field("hifi", "volume", size: 30, in: 0..11, step: 0.1)) end def test_explicit_name assert_dom_equal( - '<input id="post_title" name="dont guess" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess") + '<input id="post_title" name="dont guess" type="text" value="Hello World" />', + text_field("post", "title", "name" => "dont guess") ) assert_dom_equal( %{<textarea id="post_body" name="really!">\nBack to the hill and over it again!</textarea>}, @@ -895,17 +949,24 @@ class FormHelperTest < ActionView::TestCase '<input name="i mean it" type="hidden" value="0" /><input checked="checked" id="post_secret" name="i mean it" type="checkbox" value="1" />', check_box("post", "secret", "name" => "i mean it") ) - assert_dom_equal text_field("post", "title", "name" => "dont guess"), - text_field("post", "title", :name => "dont guess") - assert_dom_equal text_area("post", "body", "name" => "really!"), - text_area("post", "body", :name => "really!") - assert_dom_equal check_box("post", "secret", "name" => "i mean it"), - check_box("post", "secret", :name => "i mean it") + assert_dom_equal( + text_field("post", "title", "name" => "dont guess"), + text_field("post", "title", name: "dont guess") + ) + assert_dom_equal( + text_area("post", "body", "name" => "really!"), + text_area("post", "body", name: "really!") + ) + assert_dom_equal( + check_box("post", "secret", "name" => "i mean it"), + check_box("post", "secret", name: "i mean it") + ) end def test_explicit_id assert_dom_equal( - '<input id="dont guess" name="post[title]" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess") + '<input id="dont guess" name="post[title]" type="text" value="Hello World" />', + text_field("post", "title", "id" => "dont guess") ) assert_dom_equal( %{<textarea id="really!" name="post[body]">\nBack to the hill and over it again!</textarea>}, @@ -915,17 +976,24 @@ class FormHelperTest < ActionView::TestCase '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="i mean it" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret", "id" => "i mean it") ) - assert_dom_equal text_field("post", "title", "id" => "dont guess"), - text_field("post", "title", :id => "dont guess") - assert_dom_equal text_area("post", "body", "id" => "really!"), - text_area("post", "body", :id => "really!") - assert_dom_equal check_box("post", "secret", "id" => "i mean it"), - check_box("post", "secret", :id => "i mean it") + assert_dom_equal( + text_field("post", "title", "id" => "dont guess"), + text_field("post", "title", id: "dont guess") + ) + assert_dom_equal( + text_area("post", "body", "id" => "really!"), + text_area("post", "body", id: "really!") + ) + assert_dom_equal( + check_box("post", "secret", "id" => "i mean it"), + check_box("post", "secret", id: "i mean it") + ) end def test_nil_id assert_dom_equal( - '<input name="post[title]" type="text" value="Hello World" />', text_field("post", "title", "id" => nil) + '<input name="post[title]" type="text" value="Hello World" />', + text_field("post", "title", "id" => nil) ) assert_dom_equal( %{<textarea name="post[body]">\nBack to the hill and over it again!</textarea>}, @@ -943,14 +1011,22 @@ class FormHelperTest < ActionView::TestCase '<select name="post[secret]"></select>', select("post", "secret", [], {}, "id" => nil) ) - assert_dom_equal text_field("post", "title", "id" => nil), - text_field("post", "title", :id => nil) - assert_dom_equal text_area("post", "body", "id" => nil), - text_area("post", "body", :id => nil) - assert_dom_equal check_box("post", "secret", "id" => nil), - check_box("post", "secret", :id => nil) - assert_dom_equal radio_button("post", "secret", "0", "id" => nil), - radio_button("post", "secret", "0", :id => nil) + assert_dom_equal( + text_field("post", "title", "id" => nil), + text_field("post", "title", id: nil) + ) + assert_dom_equal( + text_area("post", "body", "id" => nil), + text_area("post", "body", id: nil) + ) + assert_dom_equal( + check_box("post", "secret", "id" => nil), + check_box("post", "secret", id: nil) + ) + assert_dom_equal( + radio_button("post", "secret", "0", "id" => nil), + radio_button("post", "secret", "0", id: nil) + ) end def test_index @@ -995,40 +1071,42 @@ class FormHelperTest < ActionView::TestCase ) assert_dom_equal( text_field("post", "title", "index" => 5, 'id' => nil), - text_field("post", "title", :index => 5, :id => nil) + text_field("post", "title", index: 5, id: nil) ) assert_dom_equal( text_area("post", "body", "index" => 5, 'id' => nil), - text_area("post", "body", :index => 5, :id => nil) + text_area("post", "body", index: 5, id: nil) ) assert_dom_equal( check_box("post", "secret", "index" => 5, 'id' => nil), - check_box("post", "secret", :index => 5, :id => nil) + check_box("post", "secret", index: 5, id: nil) ) end def test_auto_index pid = 123 assert_dom_equal( - "<label for=\"post_#{pid}_title\">Title</label>", + %{<label for="post_#{pid}_title">Title</label>}, label("post[]", "title") ) assert_dom_equal( - "<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title") + %{<input id="post_#{pid}_title" name="post[#{pid}][title]" type="text" value="Hello World" />}, + text_field("post[]","title") ) assert_dom_equal( - "<textarea id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\">\nBack to the hill and over it again!</textarea>", + %{<textarea id="post_#{pid}_body" name="post[#{pid}][body]">\nBack to the hill and over it again!</textarea>}, text_area("post[]", "body") ) assert_dom_equal( - "<input name=\"post[#{pid}][secret]\" type=\"hidden\" value=\"0\" /><input checked=\"checked\" id=\"post_#{pid}_secret\" name=\"post[#{pid}][secret]\" type=\"checkbox\" value=\"1\" />", + %{<input name="post[#{pid}][secret]" type="hidden" value="0" /><input checked="checked" id="post_#{pid}_secret" name="post[#{pid}][secret]" type="checkbox" value="1" />}, check_box("post[]", "secret") ) assert_dom_equal( - "<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />", + %{<input checked="checked" id="post_#{pid}_title_hello_world" name="post[#{pid}][title]" type="radio" value="Hello World" />}, radio_button("post[]", "title", "Hello World") ) - assert_dom_equal("<input id=\"post_#{pid}_title_goodbye_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />", + assert_dom_equal( + %{<input id="post_#{pid}_title_goodbye_world" name="post[#{pid}][title]" type="radio" value="Goodbye World" />}, radio_button("post[]", "title", "Goodbye World") ) end @@ -1036,48 +1114,49 @@ class FormHelperTest < ActionView::TestCase def test_auto_index_with_nil_id pid = 123 assert_dom_equal( - "<input name=\"post[#{pid}][title]\" type=\"text\" value=\"Hello World\" />", - text_field("post[]","title", :id => nil) + %{<input name="post[#{pid}][title]" type="text" value="Hello World" />}, + text_field("post[]", "title", id: nil) ) assert_dom_equal( - "<textarea name=\"post[#{pid}][body]\">\nBack to the hill and over it again!</textarea>", - text_area("post[]", "body", :id => nil) + %{<textarea name="post[#{pid}][body]">\nBack to the hill and over it again!</textarea>}, + text_area("post[]", "body", id: nil) ) assert_dom_equal( - "<input name=\"post[#{pid}][secret]\" type=\"hidden\" value=\"0\" /><input checked=\"checked\" name=\"post[#{pid}][secret]\" type=\"checkbox\" value=\"1\" />", - check_box("post[]", "secret", :id => nil) + %{<input name="post[#{pid}][secret]" type="hidden" value="0" /><input checked="checked" name="post[#{pid}][secret]" type="checkbox" value="1" />}, + check_box("post[]", "secret", id: nil) ) assert_dom_equal( -"<input checked=\"checked\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />", - radio_button("post[]", "title", "Hello World", :id => nil) + %{<input checked="checked" name="post[#{pid}][title]" type="radio" value="Hello World" />}, + radio_button("post[]", "title", "Hello World", id: nil) ) - assert_dom_equal("<input name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />", - radio_button("post[]", "title", "Goodbye World", :id => nil) + assert_dom_equal( + %{<input name="post[#{pid}][title]" type="radio" value="Goodbye World" />}, + radio_button("post[]", "title", "Goodbye World", id: nil) ) end def test_form_for_requires_block assert_raises(ArgumentError) do - form_for(:post, @post, :html => { :id => 'create-post' }) + form_for(:post, @post, html: { id: 'create-post' }) end end def test_form_for_requires_arguments error = assert_raises(ArgumentError) do - form_for(nil, :html => { :id => 'create-post' }) do + form_for(nil, html: { id: 'create-post' }) do end end assert_equal "First argument in form cannot contain nil or be empty", error.message error = assert_raises(ArgumentError) do - form_for([nil, nil], :html => { :id => 'create-post' }) do + form_for([nil, nil], html: { id: 'create-post' }) do end end assert_equal "First argument in form cannot contain nil or be empty", error.message end def test_form_for - form_for(@post, :html => { :id => 'create-post' }) do |f| + form_for(@post, html: { id: 'create-post' }) do |f| concat f.label(:title) { "The Title" } concat f.text_field(:title) concat f.text_area(:body) @@ -1089,7 +1168,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do + expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do "<label for='post_title'>The Title</label>" + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + @@ -1110,7 +1189,7 @@ class FormHelperTest < ActionView::TestCase concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) end - expected = whole_form("/posts", "new_post" , "new_post") do + expected = whole_form("/posts", "new_post", "new_post") do "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + "<label for='post_active_true'>true</label>" + "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + @@ -1123,6 +1202,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_collection_radio_buttons_with_custom_builder_block post = Post.new def post.active; false; end + form_for(post) do |f| rendered_radio_buttons = f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) do |b| b.label { b.radio_button + b.text } @@ -1130,7 +1210,7 @@ class FormHelperTest < ActionView::TestCase concat rendered_radio_buttons end - expected = whole_form("/posts", "new_post" , "new_post") do + expected = whole_form("/posts", "new_post", "new_post") do "<label for='post_active_true'>"+ "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + "true</label>" + @@ -1142,15 +1222,41 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_collection_radio_buttons_with_custom_builder_block_does_not_leak_the_template + post = Post.new + def post.active; false; end + def post.id; 1; end + + form_for(post) do |f| + rendered_radio_buttons = f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) do |b| + b.label { b.radio_button + b.text } + end + concat rendered_radio_buttons + concat f.hidden_field :id + end + + expected = whole_form("/posts", "new_post_1", "new_post") do + "<label for='post_active_true'>"+ + "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + + "true</label>" + + "<label for='post_active_false'>"+ + "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + + "false</label>"+ + "<input id='post_id' name='post[id]' type='hidden' value='1' />" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_collection_check_boxes post = Post.new def post.tag_ids; [1, 3]; end - collection = (1..3).map{|i| [i, "Tag #{i}"] } + collection = (1..3).map { |i| [i, "Tag #{i}"] } form_for(post) do |f| concat f.collection_check_boxes(:tag_ids, collection, :first, :last) end - expected = whole_form("/posts", "new_post" , "new_post") do + expected = whole_form("/posts", "new_post", "new_post") do "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + "<label for='post_tag_ids_1'>Tag 1</label>" + "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + @@ -1166,7 +1272,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_collection_check_boxes_with_custom_builder_block post = Post.new def post.tag_ids; [1, 3]; end - collection = (1..3).map{|i| [i, "Tag #{i}"] } + collection = (1..3).map { |i| [i, "Tag #{i}"] } form_for(post) do |f| rendered_check_boxes = f.collection_check_boxes(:tag_ids, collection, :first, :last) do |b| b.label { b.check_box + b.text } @@ -1174,7 +1280,7 @@ class FormHelperTest < ActionView::TestCase concat rendered_check_boxes end - expected = whole_form("/posts", "new_post" , "new_post") do + expected = whole_form("/posts", "new_post", "new_post") do "<label for='post_tag_ids_1'>" + "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + "Tag 1</label>" + @@ -1190,14 +1296,45 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_collection_check_boxes_with_custom_builder_block_does_not_leak_the_template + post = Post.new + def post.tag_ids; [1, 3]; end + def post.id; 1; end + collection = (1..3).map { |i| [i, "Tag #{i}"] } + + form_for(post) do |f| + rendered_check_boxes = f.collection_check_boxes(:tag_ids, collection, :first, :last) do |b| + b.label { b.check_box + b.text } + end + concat rendered_check_boxes + concat f.hidden_field :id + end + + expected = whole_form("/posts", "new_post_1", "new_post") do + "<label for='post_tag_ids_1'>" + + "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + + "Tag 1</label>" + + "<label for='post_tag_ids_2'>" + + "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + + "Tag 2</label>" + + "<label for='post_tag_ids_3'>" + + "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + + "Tag 3</label>" + + "<input name='post[tag_ids][]' type='hidden' value='' />"+ + "<input id='post_id' name='post[id]' type='hidden' value='1' />" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_file_field_generate_multipart Post.send :attr_accessor, :file - form_for(@post, :html => { :id => 'create-post' }) do |f| + form_for(@post, html: { id: 'create-post' }) do |f| concat f.file_field(:file) end - expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch', :multipart => true) do + expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch", multipart: true) do "<input name='post[file]' type='file' id='post_file' />" end @@ -1213,7 +1350,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form("/posts/123", "edit_post_123" , "edit_post", :method => 'patch', :multipart => true) do + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch", multipart: true) do "<input name='post[comment][file]' type='file' id='post_comment_file' />" end @@ -1221,11 +1358,11 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_format - form_for(@post, :format => :json, :html => { :id => "edit_post_123", :class => "edit_post" }) do |f| + form_for(@post, format: :json, html: { id: "edit_post_123", class: "edit_post" }) do |f| concat f.label(:title) end - expected = whole_form("/posts/123.json", "edit_post_123" , "edit_post", :method => 'patch') do + expected = whole_form("/posts/123.json", "edit_post_123", "edit_post", method: 'patch') do "<label for='post_title'>Title</label>" end @@ -1240,7 +1377,7 @@ class FormHelperTest < ActionView::TestCase concat f.submit('Edit post') end - expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => 'patch') do + expected = whole_form("/posts/44", "edit_post_44", "edit_post", method: "patch") do "<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" + "<input name='commit' type='submit' value='Edit post' />" end @@ -1249,15 +1386,15 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_symbol_object_name - form_for(@post, :as => "other_name", :html => { :id => 'create-post' }) do |f| - concat f.label(:title, :class => 'post_title') + form_for(@post, as: "other_name", html: { id: "create-post" }) do |f| + concat f.label(:title, class: 'post_title') concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) concat f.submit('Create post') end - expected = whole_form("/posts/123", "create-post", "edit_other_name", :method => 'patch') do + expected = whole_form("/posts/123", "create-post", "edit_other_name", method: "patch") do "<label for='other_name_title' class='post_title'>Title</label>" + "<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" + "<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" + @@ -1270,13 +1407,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_method_as_part_of_html_options - form_for(@post, :url => '/', :html => { :id => 'create-post', :method => :delete }) do |f| + form_for(@post, url: '/', html: { id: 'create-post', method: :delete }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form("/", "create-post", "edit_post", "delete") do + expected = whole_form("/", "create-post", "edit_post", method: "delete") do "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + @@ -1287,13 +1424,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_method - form_for(@post, :url => '/', :method => :delete, :html => { :id => 'create-post' }) do |f| + form_for(@post, url: '/', method: :delete, html: { id: 'create-post' }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form("/", "create-post", "edit_post", "delete") do + expected = whole_form("/", "create-post", "edit_post", method: "delete") do "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + @@ -1306,11 +1443,11 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_search_field # Test case for bug which would emit an "object" attribute # when used with form_for using a search_field form helper - form_for(Post.new, :url => "/search", :html => { :id => 'search-post', :method => :get}) do |f| + form_for(Post.new, url: "/search", html: { id: "search-post", method: :get }) do |f| concat f.search_field(:title) end - expected = whole_form("/search", "search-post", "new_post", "get") do + expected = whole_form("/search", "search-post", "new_post", method: "get") do "<input name='post[title]' type='search' id='post_title' />" end @@ -1318,13 +1455,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_remote - form_for(@post, :url => '/', :remote => true, :html => { :id => 'create-post', :method => :patch}) do |f| + form_for(@post, url: '/', remote: true, html: { id: 'create-post', method: :patch }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form("/", "create-post", "edit_post", :method => 'patch', :remote => true) do + expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + @@ -1335,13 +1472,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_remote_in_html - form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :patch }) do |f| + form_for(@post, url: '/', html: { remote: true, id: 'create-post', method: :patch }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form("/", "create-post", "edit_post", :method => 'patch', :remote => true) do + expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + @@ -1354,13 +1491,13 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_remote_without_html @post.persisted = false @post.stubs(:to_key).returns(nil) - form_for(@post, :remote => true) do |f| + form_for(@post, remote: true) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form("/posts", 'new_post', 'new_post', :remote => true) do + expected = whole_form("/posts", "new_post", "new_post", remote: true) do "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + @@ -1371,7 +1508,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_without_object - form_for(:post, :html => { :id => 'create-post' }) do |f| + form_for(:post, html: { id: 'create-post' }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1388,14 +1525,14 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_index - form_for(@post, :as => "post[]") do |f| + form_for(@post, as: "post[]") do |f| concat f.label(:title) concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do "<label for='post_123_title'>Title</label>" + "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" + @@ -1407,13 +1544,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_nil_index_option_override - form_for(@post, :as => "post[]", :index => nil) do |f| + form_for(@post, as: "post[]", index: nil) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" + "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[][secret]' type='hidden' value='0' />" + @@ -1425,12 +1562,12 @@ class FormHelperTest < ActionView::TestCase def test_form_for_label_error_wrapping form_for(@post) do |f| - concat f.label(:author_name, :class => 'label') + concat f.label(:author_name, class: 'label') concat f.text_field(:author_name) concat f.submit('Create post') end - expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" + "<input name='commit' type='submit' value='Create post' />" @@ -1443,12 +1580,12 @@ class FormHelperTest < ActionView::TestCase post = remove_instance_variable :@post form_for(post) do |f| - concat f.label(:author_name, :class => 'label') + concat f.label(:author_name, class: 'label') concat f.text_field(:author_name) concat f.submit('Create post') end - expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" + "<input name='commit' type='submit' value='Create post' />" @@ -1459,11 +1596,11 @@ class FormHelperTest < ActionView::TestCase def test_form_for_label_error_wrapping_block_and_non_block_versions form_for(@post) do |f| - concat f.label(:author_name, 'Name', :class => 'label') - concat f.label(:author_name, :class => 'label') { 'Name' } + concat f.label(:author_name, 'Name', class: 'label') + concat f.label(:author_name, class: 'label') { 'Name' } end - expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" + "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" end @@ -1472,13 +1609,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_namespace - form_for(@post, :namespace => 'namespace') do |f| + form_for(@post, namespace: 'namespace') do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', method: 'patch') do "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" + "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + @@ -1489,7 +1626,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_namespace_with_date_select - form_for(@post, :namespace => 'namespace') do |f| + form_for(@post, namespace: 'namespace') do |f| concat f.date_select(:written_on) end @@ -1497,12 +1634,12 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_namespace_with_label - form_for(@post, :namespace => 'namespace') do |f| + form_for(@post, namespace: 'namespace') do |f| concat f.label(:title) concat f.text_field(:title) end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', method: 'patch') do "<label for='namespace_post_title'>Title</label>" + "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" end @@ -1511,24 +1648,24 @@ class FormHelperTest < ActionView::TestCase end def test_two_form_for_with_namespace - form_for(@post, :namespace => 'namespace_1') do |f| + form_for(@post, namespace: 'namespace_1') do |f| concat f.label(:title) concat f.text_field(:title) end - expected_1 = whole_form('/posts/123', 'namespace_1_edit_post_123', 'edit_post', 'patch') do + expected_1 = whole_form('/posts/123', 'namespace_1_edit_post_123', 'edit_post', method: 'patch') do "<label for='namespace_1_post_title'>Title</label>" + "<input name='post[title]' type='text' id='namespace_1_post_title' value='Hello World' />" end assert_dom_equal expected_1, output_buffer - form_for(@post, :namespace => 'namespace_2') do |f| + form_for(@post, namespace: 'namespace_2') do |f| concat f.label(:title) concat f.text_field(:title) end - expected_2 = whole_form('/posts/123', 'namespace_2_edit_post_123', 'edit_post', 'patch') do + expected_2 = whole_form('/posts/123', 'namespace_2_edit_post_123', 'edit_post', method: 'patch') do "<label for='namespace_2_post_title'>Title</label>" + "<input name='post[title]' type='text' id='namespace_2_post_title' value='Hello World' />" end @@ -1538,7 +1675,7 @@ class FormHelperTest < ActionView::TestCase def test_fields_for_with_namespace @comment.body = 'Hello World' - form_for(@post, :namespace => 'namespace') do |f| + form_for(@post, namespace: 'namespace') do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.fields_for(@comment) { |c| @@ -1546,7 +1683,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', method: 'patch') do "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" + "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[comment][body]' type='text' id='namespace_post_comment_body' value='Hello World' />" @@ -1580,7 +1717,7 @@ class FormHelperTest < ActionView::TestCase concat f.submit end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<input name='commit' type='submit' value='Confirm Post changes' />" end @@ -1593,7 +1730,7 @@ class FormHelperTest < ActionView::TestCase old_locale, I18n.locale = I18n.locale, :submit form_for(:post) do |f| - concat f.submit :class => "extra" + concat f.submit class: "extra" end expected = whole_form do @@ -1608,11 +1745,11 @@ class FormHelperTest < ActionView::TestCase def test_submit_with_object_and_nested_lookup old_locale, I18n.locale = I18n.locale, :submit - form_for(@post, :as => :another_post) do |f| + form_for(@post, as: :another_post) do |f| concat f.submit end - expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do "<input name='commit' type='submit' value='Update your Post' />" end @@ -1629,7 +1766,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<input name='post[comment][body]' type='text' id='post_comment_body' value='Hello World' />" end @@ -1637,14 +1774,14 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for_with_nested_collections - form_for(@post, :as => 'post[]') do |f| + form_for(@post, as: 'post[]') do |f| concat f.text_field(:title) concat f.fields_for('comment[]', @comment) { |c| concat c.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />" end @@ -1653,14 +1790,14 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for_with_index_and_parent_fields - form_for(@post, :index => 1) do |c| + form_for(@post, index: 1) do |c| concat c.text_field(:title) - concat c.fields_for('comment', @comment, :index => 1) { |r| + concat c.fields_for('comment', @comment, index: 1) { |r| concat r.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<input name='post[1][title]' type='text' id='post_1_title' value='Hello World' />" + "<input name='post[1][comment][1][name]' type='text' id='post_1_comment_1_name' value='new comment' />" end @@ -1669,13 +1806,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_index_and_nested_fields_for - output_buffer = form_for(@post, :index => 1) do |f| + output_buffer = form_for(@post, index: 1) do |f| concat f.fields_for(:comment, @post) { |c| concat c.text_field(:title) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<input name='post[1][comment][title]' type='text' id='post_1_comment_title' value='Hello World' />" end @@ -1683,13 +1820,13 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for_with_index_on_both - form_for(@post, :index => 1) do |f| - concat f.fields_for(:comment, @post, :index => 5) { |c| + form_for(@post, index: 1) do |f| + concat f.fields_for(:comment, @post, index: 5) { |c| concat c.text_field(:title) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<input name='post[1][comment][5][title]' type='text' id='post_1_comment_5_title' value='Hello World' />" end @@ -1697,13 +1834,13 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for_with_auto_index - form_for(@post, :as => "post[]") do |f| + form_for(@post, as: "post[]") do |f| concat f.fields_for(:comment, @post) { |c| concat c.text_field(:title) } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do "<input name='post[123][comment][title]' type='text' id='post_123_comment_title' value='Hello World' />" end @@ -1712,12 +1849,12 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_index_radio_button form_for(@post) do |f| - concat f.fields_for(:comment, @post, :index => 5) { |c| + concat f.fields_for(:comment, @post, index: 5) { |c| concat c.radio_button(:title, "hello") } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" end @@ -1725,13 +1862,13 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for_with_auto_index_on_both - form_for(@post, :as => "post[]") do |f| + form_for(@post, as: "post[]") do |f| concat f.fields_for("comment[]", @post) { |c| concat c.text_field(:title) } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do "<input name='post[123][comment][123][title]' type='text' id='post_123_comment_123_title' value='Hello World' />" end @@ -1739,21 +1876,21 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for_with_index_and_auto_index - output_buffer = form_for(@post, :as => "post[]") do |f| - concat f.fields_for(:comment, @post, :index => 5) { |c| + output_buffer = form_for(@post, as: "post[]") do |f| + concat f.fields_for(:comment, @post, index: 5) { |c| concat c.text_field(:title) } end - output_buffer << form_for(@post, :as => :post, :index => 1) do |f| + output_buffer << form_for(@post, as: :post, index: 1) do |f| concat f.fields_for("comment[]", @post) { |c| concat c.text_field(:title) } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do "<input name='post[123][comment][5][title]' type='text' id='post_123_comment_5_title' value='Hello World' />" - end + whole_form('/posts/123', 'edit_post', 'edit_post', 'patch') do + end + whole_form('/posts/123', 'edit_post', 'edit_post', method: 'patch') do "<input name='post[1][comment][123][title]' type='text' id='post_1_comment_123_title' value='Hello World' />" end @@ -1770,7 +1907,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="new author" />' end @@ -1797,7 +1934,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' @@ -1816,7 +1953,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' @@ -1830,12 +1967,12 @@ class FormHelperTest < ActionView::TestCase form_for(@post) do |f| concat f.text_field(:title) - concat f.fields_for(:author, :include_id => false) { |af| + concat f.fields_for(:author, include_id: false) { |af| af.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' end @@ -1846,14 +1983,14 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_an_existing_record_on_a_nested_attributes_one_to_one_association_with_disabled_hidden_id_inherited @post.author = Author.new(321) - form_for(@post, :include_id => false) do |f| + form_for(@post, include_id: false) do |f| concat f.text_field(:title) concat f.fields_for(:author) { |af| af.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' end @@ -1864,14 +2001,14 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_an_existing_record_on_a_nested_attributes_one_to_one_association_with_disabled_hidden_id_override @post.author = Author.new(321) - form_for(@post, :include_id => false) do |f| + form_for(@post, include_id: false) do |f| concat f.text_field(:title) - concat f.fields_for(:author, :include_id => true) { |af| + concat f.fields_for(:author, include_id: true) { |af| af.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' @@ -1891,7 +2028,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' @@ -1912,7 +2049,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + @@ -1933,13 +2070,13 @@ class FormHelperTest < ActionView::TestCase concat af.text_field(:name) } @post.comments.each do |comment| - concat f.fields_for(:comments, comment, :include_id => false) { |cf| + concat f.fields_for(:comments, comment, include_id: false) { |cf| concat cf.text_field(:name) } end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + @@ -1954,7 +2091,7 @@ class FormHelperTest < ActionView::TestCase @post.comments = Array.new(2) { |id| Comment.new(id + 1) } @post.author = Author.new(321) - form_for(@post, :include_id => false) do |f| + form_for(@post, include_id: false) do |f| concat f.text_field(:title) concat f.fields_for(:author) { |af| concat af.text_field(:name) @@ -1966,7 +2103,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + @@ -1980,9 +2117,9 @@ class FormHelperTest < ActionView::TestCase @post.comments = Array.new(2) { |id| Comment.new(id + 1) } @post.author = Author.new(321) - form_for(@post, :include_id => false) do |f| + form_for(@post, include_id: false) do |f| concat f.text_field(:title) - concat f.fields_for(:author, :include_id => true) { |af| + concat f.fields_for(:author, include_id: true) { |af| concat af.text_field(:name) } @post.comments.each do |comment| @@ -1992,7 +2129,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + @@ -2015,7 +2152,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + @@ -2039,7 +2176,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + @@ -2062,7 +2199,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="new comment" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />' @@ -2083,7 +2220,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + @@ -2101,7 +2238,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' end @@ -2118,7 +2255,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + @@ -2139,7 +2276,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + @@ -2161,7 +2298,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + @@ -2184,7 +2321,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + @@ -2199,12 +2336,12 @@ class FormHelperTest < ActionView::TestCase @post.comments = [] form_for(@post) do |f| - concat f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf| + concat f.fields_for(:comments, Comment.new(321), child_index: 'abc') { |cf| concat cf.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end @@ -2222,12 +2359,12 @@ class FormHelperTest < ActionView::TestCase @post.comments = FakeAssociationProxy.new form_for(@post) do |f| - concat f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf| + concat f.fields_for(:comments, Comment.new(321), child_index: 'abc') { |cf| concat cf.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end @@ -2279,7 +2416,7 @@ class FormHelperTest < ActionView::TestCase @post.comments = [] form_for(@post) do |f| - f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf| + f.fields_for(:comments, Comment.new(321), child_index: 'abc') { |cf| assert_equal cf.index, 'abc' } end @@ -2313,7 +2450,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' + '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + @@ -2340,7 +2477,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="hash backed author" />' end @@ -2380,7 +2517,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_nil_index_option_override - output_buffer = fields_for("post[]", @post, :index => nil) do |f| + output_buffer = fields_for("post[]", @post, index: nil) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -2396,7 +2533,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_index_option_override - output_buffer = fields_for("post[]", @post, :index => "abc") do |f| + output_buffer = fields_for("post[]", @post, index: "abc") do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -2455,7 +2592,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_object_with_bracketed_name_and_index - output_buffer = fields_for("author[post]", @post, :index => 1) do |f| + output_buffer = fields_for("author[post]", @post, index: 1) do |f| concat f.label(:title) concat f.text_field(:title) end @@ -2470,7 +2607,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_and_fields_for - form_for(@post, :as => :post, :html => { :id => 'create-post' }) do |post_form| + form_for(@post, as: :post, html: { id: 'create-post' }) do |post_form| concat post_form.text_field(:title) concat post_form.text_area(:body) @@ -2479,7 +2616,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'create-post', 'edit_post', method: 'patch') do "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='parent_post[secret]' type='hidden' value='0' />" + @@ -2490,7 +2627,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_and_fields_for_with_object - form_for(@post, :as => :post, :html => { :id => 'create-post' }) do |post_form| + form_for(@post, as: :post, html: { id: 'create-post' }) do |post_form| concat post_form.text_field(:title) concat post_form.text_area(:body) @@ -2499,7 +2636,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'create-post', 'edit_post', method: 'patch') do "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' />" @@ -2515,7 +2652,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<input name='post[category][name]' type='text' id='post_category_name' />" end @@ -2533,13 +2670,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_labelled_builder - form_for(@post, :builder => LabelledFormBuilder) do |f| + form_for(@post, builder: LabelledFormBuilder) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" @@ -2558,7 +2695,7 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" @@ -2577,7 +2714,7 @@ class FormHelperTest < ActionView::TestCase concat f.text_field(:title) end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" end @@ -2587,7 +2724,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_labelled_builder - output_buffer = fields_for(:post, @post, :builder => LabelledFormBuilder) do |f| + output_buffer = fields_for(:post, @post, builder: LabelledFormBuilder) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -2604,7 +2741,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_labelled_builder_with_nested_fields_for_without_options_hash klass = nil - form_for(@post, :builder => LabelledFormBuilder) do |f| + form_for(@post, builder: LabelledFormBuilder) do |f| f.fields_for(:comments, Comment.new) do |nested_fields| klass = nested_fields.class '' @@ -2617,8 +2754,8 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_labelled_builder_with_nested_fields_for_with_options_hash klass = nil - form_for(@post, :builder => LabelledFormBuilder) do |f| - f.fields_for(:comments, Comment.new, :index => 'foo') do |nested_fields| + form_for(@post, builder: LabelledFormBuilder) do |f| + f.fields_for(:comments, Comment.new, index: 'foo') do |nested_fields| klass = nested_fields.class '' end @@ -2630,7 +2767,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_labelled_builder_path path = nil - form_for(@post, :builder => LabelledFormBuilder) do |f| + form_for(@post, builder: LabelledFormBuilder) do |f| path = f.to_partial_path '' end @@ -2643,8 +2780,8 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_labelled_builder_with_nested_fields_for_with_custom_builder klass = nil - form_for(@post, :builder => LabelledFormBuilder) do |f| - f.fields_for(:comments, Comment.new, :builder => LabelledFormBuilderSubclass) do |nested_fields| + form_for(@post, builder: LabelledFormBuilder) do |f| + f.fields_for(:comments, Comment.new, builder: LabelledFormBuilderSubclass) do |nested_fields| klass = nested_fields.class '' end @@ -2654,36 +2791,36 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_html_options_adds_options_to_form_tag - form_for(@post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end - expected = whole_form("/posts/123", "some_form", "some_class", 'patch') + form_for(@post, html: { id: 'some_form', class: 'some_class' }) do |f| end + expected = whole_form("/posts/123", "some_form", "some_class", method: "patch") assert_dom_equal expected, output_buffer end def test_form_for_with_string_url_option - form_for(@post, :url => 'http://www.otherdomain.com') do |f| end + form_for(@post, url: 'http://www.otherdomain.com') do |f| end - assert_equal whole_form("http://www.otherdomain.com", 'edit_post_123', 'edit_post', 'patch'), output_buffer + assert_equal whole_form("http://www.otherdomain.com", "edit_post_123", "edit_post", method: "patch"), output_buffer end def test_form_for_with_hash_url_option - form_for(@post, :url => {:controller => 'controller', :action => 'action'}) do |f| end + form_for(@post, url: { controller: 'controller', action: 'action' }) do |f| end assert_equal 'controller', @url_for_options[:controller] assert_equal 'action', @url_for_options[:action] end def test_form_for_with_record_url_option - form_for(@post, :url => @post) do |f| end + form_for(@post, url: @post) do |f| end - expected = whole_form("/posts/123", 'edit_post_123', 'edit_post', 'patch') + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") assert_equal expected, output_buffer end def test_form_for_with_existing_object form_for(@post) do |f| end - expected = whole_form("/posts/123", "edit_post_123", "edit_post", 'patch') + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") assert_equal expected, output_buffer end @@ -2702,7 +2839,7 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([@post, @comment]) {} - expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", 'patch') + expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", method: "patch") assert_dom_equal expected, output_buffer end @@ -2717,7 +2854,7 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([:admin, @post, @comment]) {} - expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", 'patch') + expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", method: "patch") assert_dom_equal expected, output_buffer end @@ -2729,15 +2866,15 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_existing_object_and_custom_url - form_for(@post, :url => "/super_posts") do |f| end + form_for(@post, url: "/super_posts") do |f| end - expected = whole_form("/super_posts", "edit_post_123", "edit_post", 'patch') + expected = whole_form("/super_posts", "edit_post_123", "edit_post", method: "patch") assert_equal expected, output_buffer end def test_form_for_with_default_method_as_patch form_for(@post) {} - expected = whole_form("/posts/123", "edit_post_123", "edit_post", "patch") + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") assert_dom_equal expected, output_buffer end @@ -2798,14 +2935,10 @@ class FormHelperTest < ActionView::TestCase txt << %{ method="#{method}">} end - def whole_form(action = "/", id = nil, html_class = nil, options = nil) + def whole_form(action = "/", id = nil, html_class = nil, options = {}) contents = block_given? ? yield : "" - if options.is_a?(Hash) - method, remote, multipart = options.values_at(:method, :remote, :multipart) - else - method = options - end + method, remote, multipart = options.values_at(:method, :remote, :multipart) form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" end diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index 56919dc592..1eed8adb62 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -27,8 +27,8 @@ class JavaScriptHelperTest < ActionView::TestCase assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos')) assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) ) assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags)) - assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!) - assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\251 newline).force_encoding('UTF-8').encode!) + assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding(Encoding::UTF_8).encode!) + assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\251 newline).force_encoding(Encoding::UTF_8).encode!) assert_equal %(dont <\\/close> tags), j(%(dont </close> tags)) end diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index 9c49438f6a..ab84bccb56 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -41,6 +41,12 @@ class RecordTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_content_tag_for_with_array_css_class + expected = %(<tr class="record_tag_post special odd" id="record_tag_post_45"></tr>) + actual = content_tag_for(:tr, @post, class: ["special", "odd"]) + assert_dom_equal expected, actual + end + def test_content_tag_for_with_prefix_and_extra_html_options expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>) actual = content_tag_for(:tr, @post, :archived, class: "special", style: "background-color: #f0f0f0") diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index c7231d9cd5..acd002ce73 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -329,6 +329,27 @@ module ActionView assert_template partial: '_partial', locals: { 'second' => '2' } end + test 'raises descriptive error message when template was not rendered' do + controller.controller_path = "test" + render(template: "test/hello_world_with_partial") + e = assert_raise ActiveSupport::TestCase::Assertion do + assert_template partial: 'i_was_never_rendered', locals: { 'did_not' => 'happen' } + end + assert_match "i_was_never_rendered to be rendered but it was not.", e.message + assert_match 'Expected ["/test/partial"] to include "i_was_never_rendered"', e.message + end + + test 'specifying locals works when the partial is inside a directory with underline prefix' do + controller.controller_path = "test" + render(template: 'test/render_partial_inside_directory') + assert_template partial: 'test/_directory/_partial_with_locales', locals: { 'name' => 'Jane' } + end + + test 'specifying locals works when the partial is inside a directory without underline prefix' do + controller.controller_path = "test" + render(template: 'test/render_partial_inside_directory') + assert_template partial: 'test/_directory/partial_with_locales', locals: { 'name' => 'Jane' } + end end module AHelperWithInitialize diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 412d13bb2b..1b2234f4e2 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -95,8 +95,8 @@ class TextHelperTest < ActionView::TestCase end def test_truncate_multibyte - assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), - truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10) + assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8), + truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8), :length => 10) end def test_truncate_does_not_modify_the_options_hash @@ -293,7 +293,7 @@ class TextHelperTest < ActionView::TestCase end def test_excerpt_with_utf8 - assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8)) + assert_equal("...\357\254\203ciency could not be...".force_encoding(Encoding::UTF_8), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding(Encoding::UTF_8), 'could', :radius => 8)) end def test_excerpt_does_not_modify_the_options_hash diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index ba65349b6a..5d87c96605 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -437,6 +437,12 @@ class UrlHelperTest < ActiveSupport::TestCase ActionDispatch::Request.new(env) end + def test_current_page_with_http_head_method + @request = request_for_url("/", :method => :head) + assert current_page?(url_hash) + assert current_page?("http://www.example.com/") + end + def test_current_page_with_simple_url @request = request_for_url("/") assert current_page?(url_hash) diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index c52e4947ae..b5562dda2e 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/array/extract_options' + module ActiveModel # == Active \Model \Callbacks # diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 49d8706ac2..38ba3cc152 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -29,6 +29,14 @@ class NamingTest < ActiveModel::TestCase assert_equal 'Track back', @model_name.human end + def test_route_key + assert_equal 'post_track_backs', @model_name.route_key + end + + def test_param_key + assert_equal 'post_track_back', @model_name.param_key + end + def test_i18n_key assert_equal :"post/track_back", @model_name.i18n_key end diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 1a40ca8efc..8b2f886cc4 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -5,13 +5,12 @@ require 'models/topic' require 'models/person' class LengthValidationTest < ActiveModel::TestCase - def teardown Topic.reset_callbacks(:validate) end def test_validates_length_of_with_allow_nil - Topic.validates_length_of( :title, :is => 5, :allow_nil => true ) + Topic.validates_length_of( :title, is: 5, allow_nil: true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").invalid? @@ -20,7 +19,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_allow_blank - Topic.validates_length_of( :title, :is => 5, :allow_blank => true ) + Topic.validates_length_of( :title, is: 5, allow_blank: true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").valid? @@ -29,7 +28,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_minimum - Topic.validates_length_of :title, :minimum => 5 + Topic.validates_length_of :title, minimum: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -51,13 +50,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_should_allow_nil - Topic.validates_length_of :title, :maximum => 10 + Topic.validates_length_of :title, maximum: 10 t = Topic.new assert t.valid? end def test_optionally_validates_length_of_using_minimum - Topic.validates_length_of :title, :minimum => 5, :allow_nil => true + Topic.validates_length_of :title, minimum: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -67,7 +66,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum - Topic.validates_length_of :title, :maximum => 5 + Topic.validates_length_of :title, maximum: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -82,7 +81,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_maximum - Topic.validates_length_of :title, :maximum => 5, :allow_nil => true + Topic.validates_length_of :title, maximum: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -92,7 +91,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within - Topic.validates_length_of(:title, :content, :within => 3..5) + Topic.validates_length_of(:title, :content, within: 3..5) t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long") assert t.invalid? @@ -111,7 +110,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within_with_exclusive_range - Topic.validates_length_of(:title, :within => 4...10) + Topic.validates_length_of(:title, within: 4...10) t = Topic.new("title" => "9 chars!!") assert t.valid? @@ -125,7 +124,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_within - Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true + Topic.validates_length_of :title, :content, within: 3..5, allow_nil: true t = Topic.new('title' => 'abc', 'content' => 'abcd') assert t.valid? @@ -135,7 +134,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is - Topic.validates_length_of :title, :is => 5 + Topic.validates_length_of :title, is: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -153,7 +152,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_is - Topic.validates_length_of :title, :is => 5, :allow_nil => true + Topic.validates_length_of :title, is: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -167,25 +166,25 @@ class LengthValidationTest < ActiveModel::TestCase bigmax = 2 ** 32 bigrange = bigmin...bigmax assert_nothing_raised do - Topic.validates_length_of :title, :is => bigmin + 5 - Topic.validates_length_of :title, :within => bigrange - Topic.validates_length_of :title, :in => bigrange - Topic.validates_length_of :title, :minimum => bigmin - Topic.validates_length_of :title, :maximum => bigmax + Topic.validates_length_of :title, is: bigmin + 5 + Topic.validates_length_of :title, within: bigrange + Topic.validates_length_of :title, in: bigrange + Topic.validates_length_of :title, minimum: bigmin + Topic.validates_length_of :title, maximum: bigmax end end def test_validates_length_of_nasty_params - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => -6) } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => 6) } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: -6) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: 6) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, minimum: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, maximum: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: "a") } end def test_validates_length_of_custom_errors_for_minimum_with_message - Topic.validates_length_of( :title, :minimum => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, minimum: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -193,7 +192,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_minimum_with_too_short - Topic.validates_length_of( :title, :minimum => 5, :too_short => "hoo %{count}" ) + Topic.validates_length_of( :title, minimum: 5, too_short: "hoo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -201,7 +200,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_maximum_with_message - Topic.validates_length_of( :title, :maximum => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, maximum: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -209,7 +208,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_in - Topic.validates_length_of(:title, :in => 10..20, :message => "hoo %{count}") + Topic.validates_length_of(:title, in: 10..20, message: "hoo %{count}") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -222,7 +221,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_maximum_with_too_long - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}" ) + Topic.validates_length_of( :title, maximum: 5, too_long: "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -230,21 +229,21 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_both_too_short_and_too_long - Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long' + Topic.validates_length_of :title, minimum: 3, maximum: 5, too_short: 'too short', too_long: 'too long' - t = Topic.new(:title => 'a') + t = Topic.new(title: 'a') assert t.invalid? assert t.errors[:title].any? assert_equal ['too short'], t.errors['title'] - t = Topic.new(:title => 'aaaaaa') + t = Topic.new(title: 'aaaaaa') assert t.invalid? assert t.errors[:title].any? assert_equal ['too long'], t.errors['title'] end def test_validates_length_of_custom_errors_for_is_with_message - Topic.validates_length_of( :title, :is => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, is: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -252,7 +251,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_is_with_wrong_length - Topic.validates_length_of( :title, :is => 5, :wrong_length => "hoo %{count}" ) + Topic.validates_length_of( :title, is: 5, wrong_length: "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -260,7 +259,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_minimum_utf8 - Topic.validates_length_of :title, :minimum => 5 + Topic.validates_length_of :title, minimum: 5 t = Topic.new("title" => "一二三四五", "content" => "whatever") assert t.valid? @@ -272,7 +271,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_utf8 - Topic.validates_length_of :title, :maximum => 5 + Topic.validates_length_of :title, maximum: 5 t = Topic.new("title" => "一二三四五", "content" => "whatever") assert t.valid? @@ -284,7 +283,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within_utf8 - Topic.validates_length_of(:title, :content, :within => 3..5) + Topic.validates_length_of(:title, :content, within: 3..5) t = Topic.new("title" => "一二", "content" => "12三四五六七") assert t.invalid? @@ -296,12 +295,12 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_within_utf8 - Topic.validates_length_of :title, :within => 3..5, :allow_nil => true + Topic.validates_length_of :title, within: 3..5, allow_nil: true - t = Topic.new(:title => "一二三四五") + t = Topic.new(title: "一二三四五") assert t.valid?, t.errors.inspect - t = Topic.new(:title => "一二三") + t = Topic.new(title: "一二三") assert t.valid?, t.errors.inspect t.title = nil @@ -309,7 +308,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is_utf8 - Topic.validates_length_of :title, :is => 5 + Topic.validates_length_of :title, is: 5 t = Topic.new("title" => "一二345", "content" => "whatever") assert t.valid? @@ -321,9 +320,9 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_block - Topic.validates_length_of :content, :minimum => 5, :too_short => "Your essay must be at least %{count} words.", - :tokenizer => lambda {|str| str.scan(/\w+/) } - t = Topic.new(:content => "this content should be long enough") + Topic.validates_length_of :content, minimum: 5, too_short: "Your essay must be at least %{count} words.", + tokenizer: lambda {|str| str.scan(/\w+/) } + t = Topic.new(content: "this content should be long enough") assert t.valid? t.content = "not long enough" @@ -333,18 +332,18 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_for_fixnum - Topic.validates_length_of(:approved, :is => 4) + Topic.validates_length_of(:approved, is: 4) - t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1) + t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1) assert t.invalid? assert t.errors[:approved].any? - t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1234) + t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1234) assert t.valid? end def test_validates_length_of_for_ruby_class - Person.validates_length_of :karma, :minimum => 5 + Person.validates_length_of :karma, minimum: 5 p = Person.new p.karma = "Pix" @@ -359,7 +358,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_for_infinite_maxima - Topic.validates_length_of(:title, :within => 5..Float::INFINITY) + Topic.validates_length_of(:title, within: 5..Float::INFINITY) t = Topic.new("title" => "1234") assert t.invalid? @@ -368,7 +367,7 @@ class LengthValidationTest < ActiveModel::TestCase t.title = "12345" assert t.valid? - Topic.validates_length_of(:author_name, :maximum => Float::INFINITY) + Topic.validates_length_of(:author_name, maximum: Float::INFINITY) assert t.valid? @@ -377,13 +376,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_should_not_allow_nil_when_nil_not_allowed - Topic.validates_length_of :title, :maximum => 10, :allow_nil => false + Topic.validates_length_of :title, maximum: 10, allow_nil: false t = Topic.new assert t.invalid? end def test_validates_length_of_using_maximum_should_not_allow_nil_and_empty_string_when_blank_not_allowed - Topic.validates_length_of :title, :maximum => 10, :allow_blank => false + Topic.validates_length_of :title, maximum: 10, allow_blank: false t = Topic.new assert t.invalid? @@ -392,13 +391,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_both_minimum_and_maximum_should_not_allow_nil - Topic.validates_length_of :title, :minimum => 5, :maximum => 10 + Topic.validates_length_of :title, minimum: 5, maximum: 10 t = Topic.new assert t.invalid? end def test_validates_length_of_using_minimum_0_should_not_allow_nil - Topic.validates_length_of :title, :minimum => 0 + Topic.validates_length_of :title, minimum: 0 t = Topic.new assert t.invalid? @@ -407,11 +406,19 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is_0_should_not_allow_nil - Topic.validates_length_of :title, :is => 0 + Topic.validates_length_of :title, is: 0 t = Topic.new assert t.invalid? t.title = "" assert t.valid? end + + def test_validates_with_diff_in_option + Topic.validates_length_of(:title, is: 5) + Topic.validates_length_of(:title, is: 5, if: Proc.new { false } ) + + assert Topic.new("title" => "david").valid? + assert Topic.new("title" => "david2").invalid? + end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 5a0c391154..4489ca1aff 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,169 @@ ## Rails 4.0.0 (unreleased) ## +* Preloading `has_many :through` associations with conditions won't + cache the `:through` association. This will prevent invalid + subsets to be cached. + Fixes #8423. + + Example: + + class User + has_many :posts + has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, :through => :posts + end + + a_user = User.includes(:recent_comments).first + + # this is preloaded + a_user.recent_comments + + # fetching the recent_comments through the posts association won't preload it. + a_user.posts + + *Yves Senn* + +* Don't run after_commit callback when creating through an association + if saving the record fails. + + *James Miller * + +* Allow store accessors to be overrided like other attribute methods, e.g.: + + class User < ActiveRecord::Base + store :settings, accessors: [ :color, :homepage ], coder: JSON + + def color + super || 'red' + end + end + + *Sergey Nartimov* + +* Quote numeric values being compared to non-numeric columns. Otherwise, + in some database, the string column values will be coerced to a numeric + allowing 0, 0.0 or false to match any string starting with a non-digit. + + Example: + + App.where(apikey: 0) # => SELECT * FROM users WHERE apikey = '0' + + *Dylan Smith* + +* Schema dumper supports dumping the enabled database extensions to `schema.rb` + (currently only supported by postgresql). + + *Justin George* + +* The `DATABASE_URL` environment variable now converts ints, floats, and + the strings true and false to Ruby types. 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: + + Example: + + DATABASE_URL=sqlite3://localhost/test_db?timeout=500 + DATABASE_URL=postgresql://localhost/test_db?prepared_statements=false + + *Aaron Stone* + +* `Relation#merge` now only overwrites where values on the LHS of the + merge. Consider: + + left = Person.where(age: [13, 14, 15]) + right = Person.where(age: [13, 14]).where(age: [14, 15]) + + `left` results in the following SQL: + + WHERE age IN (13, 14, 15) + + `right` results in the following SQL: + + WHERE age IN (13, 14) AND age IN (14, 15) + + Previously, `left.merge(right)` would result in all but the last + condition being removed: + + WHERE age IN (14, 15) + + Now it results in the LHS condition(s) for `age` being removed, but + the RHS remains as it is: + + WHERE age IN (13, 14) AND age IN (14, 15) + + *Jon Leighton* + +* Fix handling of dirty time zone aware attributes + + Previously, when `time_zone_aware_attributes` were enabled, after + changing a datetime or timestamp attribute and then changing it back + to the original value, `changed_attributes` still tracked the + attribute as changed. This caused `[attribute]_changed?` and + `changed?` methods to return true incorrectly. + + Example: + + in_time_zone 'Paris' do + order = Order.new + original_time = Time.local(2012, 10, 10) + order.shipped_at = original_time + order.save + order.changed? # => false + + # changing value + order.shipped_at = Time.local(2013, 1, 1) + order.changed? # => true + + # reverting to original value + order.shipped_at = original_time + order.changed? # => false, used to return true + end + + *Lilibeth De La Cruz* + +* When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`. + Fix #6865. + + Example: + + relation.uniq.count # => SELECT COUNT(DISTINCT *) + + *Yves Senn + Kaspar Schiess* + +* PostgreSQL ranges type support. Includes: int4range, int8range, + numrange, tsrange, tstzrange, daterange + + Ranges can be created with inclusive and exclusive bounds. + + Example: + + create_table :Room do |t| + t.daterange :availability + end + + Room.create(availability: (Date.today..Float::INFINITY)) + Room.first.availability # => Wed, 19 Sep 2012..Infinity + + One thing to note: Range class does not support exclusive lower + bound. + + *Alexander Grebennik* + +* Added a state instance variable to each transaction. Will allow other objects + to know whether a transaction has been committed or rolled back. + + *John Wang* + +* Collection associations `#empty?` always respects builded records. + Fix #8879. + + Example: + + widget = Widget.new + widget.things.build + widget.things.empty? # => false + + *Yves Senn* + * Remove support for parsing YAML parameters from request. *Aaron Patterson* @@ -481,7 +645,7 @@ After: - #=> SELECT * FROM users WHERE 1 = 2; + #=> SELECT * FROM users WHERE 1=0; *Damien Mathieu* @@ -503,7 +667,7 @@ *Matt Jones* -* Accept belongs_to (including polymorphic) association keys in queries. +* Accept `belongs_to` (including polymorphic) association keys in queries. The following queries are now equivalent: @@ -974,7 +1138,6 @@ * `:conditions` becomes `:where`. * `:include` becomes `:includes`. - * `:extend` becomes `:extending`. The code to implement the deprecated features has been moved out to the `activerecord-deprecated_finders` gem. This gem is a dependency diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS.rdoc index bdd8834dcb..bdd8834dcb 100644 --- a/activerecord/RUNNING_UNIT_TESTS +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 53ddff420e..0523314128 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -39,6 +39,11 @@ namespace :test do end end +namespace :db do + task :create => ['mysql:build_databases', 'postgresql:build_databases'] + task :drop => ['mysql:drop_databases', 'postgresql:drop_databases'] +end + %w( mysql mysql2 postgresql sqlite3 sqlite3_mem firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| Rake::TestTask.new("test_#{adapter}") { |t| adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 31ddb01123..d523c1eca1 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_dependency 'activemodel', version s.add_dependency 'arel', '~> 3.0.2' - s.add_dependency 'activerecord-deprecated_finders', '0.0.1' + 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 16a46a59d1..06bdabfced 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -191,7 +191,7 @@ module ActiveRecord # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> - # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(mileston), Project#milestones.find(milestone_id),</tt> + # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt> # <tt>Project#milestones.build, Project#milestones.create</tt> # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt> diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 1303822868..c5fb1fe2c7 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -15,7 +15,7 @@ module ActiveRecord def scope scope = klass.unscoped - scope.merge! eval_scope(klass, reflection.scope) if reflection.scope + scope.extending! Array(options[:extend]) add_constraints(scope) end @@ -58,7 +58,7 @@ module ActiveRecord if reflection.source_macro == :belongs_to if reflection.options[:polymorphic] - key = reflection.association_primary_key(klass) + key = reflection.association_primary_key(self.klass) else key = reflection.association_primary_key end @@ -91,8 +91,13 @@ module ActiveRecord # Exclude the scope of the association itself, because that # was already merged in the #scope method. - (scope_chain[i] - [self.reflection.scope]).each do |scope_chain_item| - item = eval_scope(reflection.klass, scope_chain_item) + scope_chain[i].each do |scope_chain_item| + klass = i == 0 ? self.klass : reflection.klass + item = eval_scope(klass, scope_chain_item) + + if scope_chain_item == self.reflection.scope + scope.merge! item.except(:where, :includes) + end scope.includes! item.includes_values scope.where_values += item.where_values diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index fcdfc1e150..fdead16761 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -6,7 +6,8 @@ module ActiveRecord::Associations::Builder CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] def valid_options - super + [:table_name, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove] + super + [:table_name, :finder_sql, :counter_sql, :before_add, + :after_add, :before_remove, :after_remove, :extend] end attr_reader :block_extension, :extension_module diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 832b963052..5feb149946 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -273,7 +273,7 @@ module ActiveRecord if loaded? || options[:counter_sql] size.zero? else - !scope.exists? + @target.blank? && !scope.exists? end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 33dce58982..e93e700c93 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -33,6 +33,7 @@ module ActiveRecord def initialize(klass, association) #:nodoc: @association = association super klass, klass.arel_table + self.default_scoped = true merge! association.scope(nullify: false) end diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index cd366ac8b7..f40368cfeb 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -68,7 +68,7 @@ module ActiveRecord remove_duplicate_results!(base, records, association) end when Hash - associations.keys.each do |name| + associations.each_key do |name| reflection = base.reflections[name] remove_uniq_by_reflection(reflection, records) diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 1c1ba11c44..b0b1c13b0d 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -31,7 +31,8 @@ module ActiveRecord through_records = Array.wrap(owner.send(through_reflection.name)) # Dont cache the association - we would only be caching a subset - if reflection.options[:source_type] && through_reflection.collection? + if (through_scope != through_reflection.klass.unscoped) || + (reflection.options[:source_type] && through_reflection.collection?) owner.association(through_reflection.name).reset end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 47a8b576c0..41b5a6e926 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -33,20 +33,12 @@ module ActiveRecord def define_method_attribute=(attr_name) if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) method_body, line = <<-EOV, __LINE__ + 1 - def #{attr_name}=(original_time) - original_time = nil if original_time.blank? - time = original_time - unless time.acts_like?(:time) - time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time - end - zoned_time = time && time.in_time_zone rescue nil - rounded_time = round_usec(zoned_time) - rounded_value = round_usec(read_attribute("#{attr_name}")) - if (rounded_value != rounded_time) || (!rounded_value && original_time) - write_attribute("#{attr_name}", original_time) - #{attr_name}_will_change! - @attributes_cache["#{attr_name}"] = zoned_time - end + def #{attr_name}=(time) + time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil + previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name}) + write_attribute(:#{attr_name}, time) + #{attr_name}_will_change! if previous_time != time_with_zone + @attributes_cache["#{attr_name}"] = time_with_zone end EOV generated_attribute_methods.module_eval(method_body, __FILE__, line) @@ -62,11 +54,6 @@ module ActiveRecord [:datetime, :timestamp].include?(column.type) end end - - private - def round_usec(value) - value.change(usec: 0) if value - end end end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index bf5793d454..e262401da6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -162,8 +162,8 @@ module ActiveRecord #:nodoc: # # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects # by simple queries without turning to SQL. They work by appending the name of an attribute - # to <tt>find_by_</tt> # like <tt>Person.find_by_user_name</tt>. - # Instead of writing # <tt>Person.where(user_name: user_name).first</tt>, you just do + # to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>. + # Instead of writing <tt>Person.where(user_name: user_name).first</tt>, you just do # <tt>Person.find_by_user_name(user_name)</tt>. # # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 27e6e8898c..847d9da6e6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -237,7 +237,7 @@ module ActiveRecord @spec = spec @checkout_timeout = spec.config[:checkout_timeout] || 5 - @dead_connection_timeout = spec.config[:dead_connection_timeout] + @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5 @reaper = Reaper.new self, spec.config[:reaping_frequency] @reaper.run @@ -517,6 +517,7 @@ module ActiveRecord def establish_connection(owner, spec) @class_to_pool.clear + raise RuntimeError, "Anonymous class is not allowed." unless owner.name owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index be6fda95b4..41e07fbda9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -9,9 +9,9 @@ module ActiveRecord def dirties_query_cache(base, *method_names) method_names.each do |method_name| base.class_eval <<-end_code, __FILE__, __LINE__ + 1 - def #{method_name}(*) # def update_with_query_dirty(*args) + def #{method_name}(*) # def update_with_query_dirty(*) clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled - super # update_without_query_dirty(*args) + super # super end # end end_code end @@ -85,6 +85,8 @@ module ActiveRecord end end + # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such + # queries should not be cached. def locked?(arel) arel.respond_to?(:locked) && arel.locked end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 60a9eee7c7..aec4654eee 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -25,13 +25,19 @@ 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 BigDecimal then value.to_s('F') - when Numeric, ActiveSupport::Duration then value.to_s + 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 Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value.to_s}'" @@ -93,6 +99,18 @@ module ActiveRecord quote_column_name(table_name) end + # Override to return the quoted table name for assignment. Defaults to + # table quoting. + # + # This works for mysql and mysql2 where table.column can be used to + # resolve ambiguity. + # + # We override this in the sqlite and postgresql adapters to use only + # the column name (as per syntax requirements). + def quote_table_name_for_assignment(table, attr) + quote_table_name("#{table}.#{attr}") + end + def quoted_true "'t'" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index b1ec33d06c..f758e19a4f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -252,15 +252,12 @@ module ActiveRecord self end - %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV, __FILE__, __LINE__ + 1 - def #{column_type}(*args) # def string(*args) - options = args.extract_options! # options = args.extract_options! - column_names = args # column_names = args - type = :'#{column_type}' # type = :string - column_names.each { |name| column(name, type, options) } # column_names.each { |name| column(name, type, options) } - end # end - EOV + [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| + define_method column_type do |*args| + options = args.extract_options! + column_names = args + column_names.each { |name| column(name, column_type, options) } + end end # Adds index options to the indexes hash, keyed by column name @@ -486,15 +483,13 @@ module ActiveRecord # # t.string(:goat) # t.string(:goat, :sheep) - %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV, __FILE__, __LINE__ + 1 - def #{column_type}(*args) # def string(*args) - options = args.extract_options! # options = args.extract_options! - args.each do |name| # column_names.each do |name| - @base.add_column(@table_name, name, :#{column_type}, options) # @base.add_column(@table_name, name, :string, options) - end # end - end # end - EOV + [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| + define_method column_type do |*args| + options = args.extract_options! + args.each do |name| + @base.add_column(@table_name, name, column_type, options) + 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 9d6111b51e..fd5eaab9c9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -47,6 +47,9 @@ module ActiveRecord value.to_s when Date, DateTime, Time "'#{value.to_s(:db)}'" + when Range + # infinity dumps as Infinity, which causes uninitialized constant error + value.inspect.gsub('Infinity', '::Float::INFINITY') else value.inspect end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 4cca94e40b..3ecef96b10 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -5,6 +5,35 @@ module ActiveRecord def initialize(connection) @connection = connection + @state = TransactionState.new + end + + def state + @state + end + end + + class TransactionState + + VALID_STATES = Set.new([:committed, :rolledback, nil]) + + def initialize(state = nil) + @state = state + end + + def committed? + @state == :committed + end + + def rolledback? + @state == :rolledback + end + + def set_state(state) + if !VALID_STATES.include?(state) + raise ArgumentError, "Invalid transaction state: #{state}" + end + @state = state end end @@ -91,6 +120,7 @@ module ActiveRecord end def rollback_records + @state.set_state(:rolledback) records.uniq.each do |record| begin record.rolledback!(parent.closed?) @@ -101,6 +131,7 @@ module ActiveRecord end def commit_records + @state.set_state(:committed) records.uniq.each do |record| begin record.committed! diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index cbb6869e66..2400fc92cc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -171,10 +171,22 @@ module ActiveRecord false end + # Does this adapter support database extensions? As of this writing + # only postgresql does. + def supports_extensions? + false + end + + # A list of extensions, to be filled in by databases that + # support them (at the moment, postgresql). + def extensions + [] + end + # QUOTING ================================================== # Returns a bind substitution value given a +column+ and list of current - # +binds+ + # +binds+. def substitute_at(column, index) Arel::Nodes::BindParam.new '?' end @@ -293,6 +305,10 @@ module ActiveRecord pool.checkin self end + def valid_type?(type) + true + end + protected def log(sql, name = "SQL", binds = []) 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 52b0b3fe79..e3ad5594d2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -212,8 +212,6 @@ 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 @@ -584,6 +582,10 @@ module ActiveRecord @config.fetch(:strict, true) end + def valid_type?(type) + !native_database_types[type].nil? + end + protected # MySQL is too stupid to create a temporary table for use subquery, so we have @@ -749,7 +751,6 @@ module ActiveRecord # ...and send them all in one query execute("SET #{encoding} #{variable_assignments}", :skip_logging) end - end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index fb28ecb6cf..a4b3a0c584 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -74,12 +74,13 @@ module ActiveRecord def type_cast_for_write(value) return value unless number? - if value == false + case value + when FalseClass 0 - elsif value == true + when TrueClass 1 - elsif value.is_a?(String) && value.blank? - nil + when String + value.presence else value end @@ -126,7 +127,6 @@ module ActiveRecord when :hstore then "#{klass}.string_to_hstore(#{var_name})" when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})" when :json then "#{klass}.string_to_json(#{var_name})" - when :intrange then "#{klass}.string_to_intrange(#{var_name})" else var_name end end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 09250d3c01..577a362568 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -62,6 +62,10 @@ 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 connection_url_to_hash(url) # :nodoc: config = URI.parse url adapter = config.scheme @@ -72,15 +76,38 @@ module ActiveRecord :port => config.port, :database => config.path.sub(%r{^/},""), :host => config.host } + spec.reject!{ |_,value| value.blank? } + uri_parser = URI::Parser.new + spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) } + 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 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/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index f7d734a2f1..3d8f0b575c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -62,6 +62,12 @@ module ActiveRecord "{#{casted_values.join(',')}}" end + def range_to_string(object) + from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin + to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end + "[#{from},#{to}#{object.exclude_end? ? ')' : ']'}" + end + def string_to_json(string) if String === string ActiveSupport::JSON.decode(string) @@ -92,36 +98,6 @@ module ActiveRecord parse_pg_array(string).map{|val| oid.type_cast val} end - def string_to_intrange(string) - if string.nil? - nil - elsif "empty" == string - (nil..nil) - elsif String === string && (matches = /^(\(|\[)([0-9]+),(\s?)([0-9]+)(\)|\])$/i.match(string)) - lower_bound = ("(" == matches[1] ? (matches[2].to_i + 1) : matches[2].to_i) - upper_bound = (")" == matches[5] ? (matches[4].to_i - 1) : matches[4].to_i) - (lower_bound..upper_bound) - else - string - end - end - - def intrange_to_string(object) - if object.nil? - nil - elsif Range === object - if [object.first, object.last].all? { |el| Integer === el } - "[#{object.first.to_i},#{object.exclude_end? ? object.last.to_i : object.last.to_i + 1})" - elsif [object.first, object.last].all? { |el| NilClass === el } - "empty" - else - nil - end - else - object - end - end - private HstorePair = begin diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 02c295983f..e09319890a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -78,6 +78,64 @@ module ActiveRecord end end + class Range < Type + attr_reader :subtype + def initialize(subtype) + @subtype = subtype + end + + def extract_bounds(value) + from, to = value[1..-2].split(',') + { + from: (value[1] == ',' || from == '-infinity') ? infinity(:negative => true) : from, + to: (value[-2] == ',' || to == 'infinity') ? infinity : to, + exclude_start: (value[0] == '('), + exclude_end: (value[-1] == ')') + } + end + + def infinity(options = {}) + ::Float::INFINITY * (options[:negative] ? -1 : 1) + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + + def to_integer(value) + infinity?(value) ? value : value.to_i + end + + def type_cast(value) + return if value.nil? || value == 'empty' + return value if value.is_a?(::Range) + + extracted = extract_bounds(value) + + case @subtype + when :date + from = ConnectionAdapters::Column.value_to_date(extracted[:from]) + from -= 1.day if extracted[:exclude_start] + to = ConnectionAdapters::Column.value_to_date(extracted[:to]) + when :decimal + from = BigDecimal.new(extracted[:from].to_s) + # FIXME: add exclude start for ::Range, same for timestamp ranges + to = BigDecimal.new(extracted[:to].to_s) + when :time + from = ConnectionAdapters::Column.string_to_time(extracted[:from]) + to = ConnectionAdapters::Column.string_to_time(extracted[:to]) + when :integer + from = to_integer(extracted[:from]) rescue value ? 1 : 0 + from -= 1 if extracted[:exclude_start] + to = to_integer(extracted[:to]) rescue value ? 1 : 0 + else + return value + end + + ::Range.new(from, to, extracted[:exclude_end]) + end + end + class Integer < Type def type_cast(value) return if value.nil? @@ -168,14 +226,6 @@ module ActiveRecord end end - class IntRange < Type - def type_cast(value) - return if value.nil? - - ConnectionAdapters::PostgreSQLColumn.string_to_intrange value - end - end - class TypeMap def initialize @mapping = {} @@ -189,6 +239,10 @@ module ActiveRecord @mapping[oid] end + def clear + @mapping.clear + end + def key?(oid) @mapping.key? oid end @@ -241,6 +295,13 @@ module ActiveRecord alias_type 'int8', 'int2' alias_type 'oid', 'int2' + register_type 'daterange', OID::Range.new(:date) + register_type 'numrange', OID::Range.new(:decimal) + register_type 'tsrange', OID::Range.new(:time) + register_type 'int4range', OID::Range.new(:integer) + alias_type 'tstzrange', 'tsrange' + alias_type 'int8range', 'int4range' + register_type 'numeric', OID::Decimal.new register_type 'text', OID::Identity.new alias_type 'varchar', 'text' @@ -278,9 +339,6 @@ module ActiveRecord register_type 'json', OID::Json.new register_type 'ltree', OID::Identity.new - register_type 'int4range', OID::IntRange.new - alias_type 'int8range', 'int4range' - register_type 'cidr', OID::Cidr.new alias_type 'inet', 'cidr' end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index c2fcef94da..47e2e3928f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -19,6 +19,12 @@ module ActiveRecord return super unless column case value + when Range + if /range$/ =~ column.sql_type + "'#{PostgreSQLColumn.range_to_string(value)}'::#{column.sql_type}" + else + super + end when Array if column.array "'#{PostgreSQLColumn.array_to_string(value, column, self)}'" @@ -31,11 +37,6 @@ module ActiveRecord when 'json' then super(PostgreSQLColumn.json_to_string(value), column) else super end - when Range - case column.sql_type - when 'int4range', 'int8range' then super(PostgreSQLColumn.intrange_to_string(value), column) - else super - end when IPAddr case column.sql_type when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column) @@ -74,6 +75,9 @@ module ActiveRecord return super(value, column) unless column case value + when Range + return super(value, column) unless /range$/ =~ column.sql_type + PostgreSQLColumn.range_to_string(value) when NilClass if column.array && array_member 'NULL' @@ -94,11 +98,6 @@ module ActiveRecord when 'json' then PostgreSQLColumn.json_to_string(value) else super(value, column) end - when Range - case column.sql_type - when 'int4range', 'int8range' then PostgreSQLColumn.intrange_to_string(value) - else super(value, column) - end when IPAddr return super(value, column) unless ['inet','cidr'].include? column.sql_type PostgreSQLColumn.cidr_to_string(value) @@ -131,6 +130,10 @@ module ActiveRecord end end + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + # Quotes column names for use in SQL queries. def quote_column_name(name) #:nodoc: PGconn.quote_ident(name.to_s) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 8c68576bdc..73ca2c8e61 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -417,14 +417,6 @@ module ActiveRecord when 0..6; "timestamp(#{precision})" else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6") end - when 'intrange' - return 'int4range' unless limit - - case limit - when 1..4; 'int4range' - when 5..8; 'int8range' - else raise(ActiveRecordError, "No range type has byte size #{limit}. Use a numeric with precision 0 instead.") - end else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b1b0467379..2af60ad0a2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -77,6 +77,8 @@ module ActiveRecord return default unless default case default + when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m + $1 # Numeric types when /\A\(?(-?\d+(\.\d*)?\)?)\z/ $1 @@ -117,9 +119,6 @@ module ActiveRecord # JSON when /\A'(.*)'::json\z/ $1 - # int4range, int8range - when /\A'(.*)'::int(4|8)range\z/ - $1 # Object identifier types when /\A-?\d+\z/ $1 @@ -220,12 +219,11 @@ module ActiveRecord # JSON type when 'json' :json - # int4range, int8range types - when 'int4range', 'int8range' - :intrange # Small and big integer types when /^(?:small|big)int$/ :integer + when /(num|date|tstz|ts|int4|int8)range$/ + field_type.to_sym # Pass through all types that are not specific to PostgreSQL. else super @@ -276,6 +274,30 @@ module ActiveRecord column(args[0], 'tsvector', options) end + def int4range(name, options = {}) + column(name, 'int4range', options) + end + + def int8range(name, options = {}) + column(name, 'int8range', options) + end + + def tsrange(name, options = {}) + column(name, 'tsrange', options) + end + + def tstzrange(name, options = {}) + column(name, 'tstzrange', options) + end + + def numrange(name, options = {}) + column(name, 'numrange', options) + end + + def daterange(name, options = {}) + column(name, 'daterange', options) + end + def hstore(name, options = {}) column(name, 'hstore', options) end @@ -304,10 +326,6 @@ module ActiveRecord column(name, 'json', options) end - def intrange(name, options = {}) - column(name, 'intrange', options) - end - def column(name, type = nil, options = {}) super column = self[name] @@ -339,6 +357,12 @@ module ActiveRecord timestamp: { name: "timestamp" }, time: { name: "time" }, date: { name: "date" }, + daterange: { name: "daterange" }, + numrange: { name: "numrange" }, + tsrange: { name: "tsrange" }, + tstzrange: { name: "tstzrange" }, + int4range: { name: "int4range" }, + int8range: { name: "int8range" }, binary: { name: "bytea" }, boolean: { name: "boolean" }, xml: { name: "xml" }, @@ -349,7 +373,6 @@ module ActiveRecord macaddr: { name: "macaddr" }, uuid: { name: "uuid" }, json: { name: "json" }, - intrange: { name: "int4range" }, ltree: { name: "ltree" } } @@ -552,6 +575,45 @@ module ActiveRecord true end + # Returns true if pg > 9.2 + def supports_extensions? + postgresql_version >= 90200 + end + + # Range datatypes weren't introduced until PostgreSQL 9.2 + def supports_ranges? + postgresql_version >= 90200 + end + + def enable_extension(name) + exec_query("CREATE EXTENSION IF NOT EXISTS #{name}").tap { + reload_type_map + } + end + + def disable_extension(name) + exec_query("DROP EXTENSION IF EXISTS #{name} CASCADE").tap { + reload_type_map + } + end + + def extension_enabled?(name) + if supports_extensions? + res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)", + 'SCHEMA' + res.column_types['exists'].type_cast res.rows.first.first + end + end + + def extensions + if supports_extensions? + res = exec_query "SELECT extname from pg_extension", "SCHEMA" + res.rows.map { |r| res.column_types['extname'].type_cast r.first } + else + super + end + end + # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i @@ -593,6 +655,10 @@ module ActiveRecord @use_insert_returning end + def valid_type?(type) + !native_database_types[type].nil? + end + protected # Returns the version of the connected PostgreSQL server. @@ -617,6 +683,11 @@ module ActiveRecord private + def reload_type_map + OID::TYPE_MAP.clear + initialize_type_map + end + def initialize_type_map result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA') leaves, nodes = result.partition { |row| row['typelem'] == '0' } diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 11e8197293..91444950be 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -229,6 +229,10 @@ module ActiveRecord @connection.class.quote(s) end + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + def quote_column_name(name) #:nodoc: %Q("#{name.to_s.gsub('"', '""')}") end @@ -598,7 +602,6 @@ module ActiveRecord super end end - end end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 94c6684700..63a1197a56 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -365,17 +365,18 @@ module ActiveRecord pk = self.class.primary_key @attributes[pk] = nil unless @attributes.key?(pk) - @aggregation_cache = {} - @association_cache = {} - @attributes_cache = {} - @previously_changed = {} - @changed_attributes = {} - @readonly = false - @destroyed = false - @marked_for_destruction = false - @new_record = true - @txn = nil + @aggregation_cache = {} + @association_cache = {} + @attributes_cache = {} + @previously_changed = {} + @changed_attributes = {} + @readonly = false + @destroyed = false + @marked_for_destruction = false + @new_record = true + @txn = nil @_start_transaction_state = {} + @transaction = nil end end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 5a4ef5991d..2958d08210 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -363,11 +363,11 @@ module ActiveRecord # # first: # name: Smurf - # *DEFAULTS + # <<: *DEFAULTS # # second: # name: Fraggle - # *DEFAULTS + # <<: *DEFAULTS # # Any fixture labeled "DEFAULTS" is safely ignored. class FixtureSet diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 67339c05e5..823595a128 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -342,7 +342,7 @@ module ActiveRecord end def call(env) - ActiveRecord::Base.logger.quietly do + ActiveRecord::Base.logger.silence do ActiveRecord::Migration.check_pending! end @app.call(env) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 3011f959a5..803cae7115 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -220,7 +220,7 @@ module ActiveRecord save end end - + alias update_attributes update # Updates its receiver just like +update+ but calls <tt>save!</tt> instead @@ -233,29 +233,29 @@ module ActiveRecord save! end end - + alias update_attributes! update! - # Updates a single attribute of an object, without having to explicitly call save on that object. - # - # * Validation is skipped. - # * Callbacks are skipped. - # * updated_at/updated_on column is not updated if that column is available. - # - # Raises an +ActiveRecordError+ when called on new objects, or when the +name+ - # attribute is marked as readonly. + # Equivalent to <code>update_columns(name => value)</code>. def update_column(name, value) update_columns(name => value) end - # Updates the attributes from the passed-in hash, without having to explicitly call save on that object. + # Updates the attributes directly in the database issuing an UPDATE SQL + # statement and sets them in the receiver: # - # * Validation is skipped. + # user.update_columns(last_request_at: Time.current) + # + # This is the fastest way to update attributes because it goes straight to + # the database, but take into account that in consequence the regular update + # procedures are totally bypassed. In particular: + # + # * Validations are skipped. # * Callbacks are skipped. - # * updated_at/updated_on column is not updated if that column is available. + # * +updated_at+/+updated_on+ are not updated. # - # Raises an +ActiveRecordError+ when called on new objects, or when at least - # one of the attributes is marked as readonly. + # This method raises an +ActiveRecord::ActiveRecordError+ when called on new + # objects, or when at least one of the attributes is marked as readonly. def update_columns(attributes) raise ActiveRecordError, "can not update on a new record object" unless persisted? diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 5ddcaee6be..f08b9c614d 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -1,4 +1,3 @@ - module ActiveRecord module Querying delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 259d0ff12b..f36af7182f 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -284,8 +284,6 @@ db_namespace = namespace :db do filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") current_config = ActiveRecord::Tasks::DatabaseTasks.current_config case current_config['adapter'] - when /mysql/, /postgresql/, /sqlite/ - ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) when 'oci', 'oracle' ActiveRecord::Base.establish_connection(current_config) File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump } @@ -296,7 +294,7 @@ db_namespace = namespace :db do db_string = firebird_db_string(current_config) sh "isql -a #{db_string} > #{filename}" else - raise "Task not supported by '#{current_config["adapter"]}'" + ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) end if ActiveRecord::Base.connection.supports_migrations? @@ -312,8 +310,6 @@ db_namespace = namespace :db do current_config = ActiveRecord::Tasks::DatabaseTasks.current_config filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") case current_config['adapter'] - when /mysql/, /postgresql/, /sqlite/ - ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename) when 'sqlserver' `sqlcmd -S #{current_config['host']} -d #{current_config['database']} -U #{current_config['username']} -P #{current_config['password']} -i #{filename}` when 'oci', 'oracle' @@ -326,7 +322,7 @@ db_namespace = namespace :db do db_string = firebird_db_string(current_config) sh "isql -i #{filename} #{db_string}" else - raise "Task not supported by '#{current_config['adapter']}'" + ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename) end end @@ -384,8 +380,6 @@ db_namespace = namespace :db do task :purge => [:environment, :load_config] do abcs = ActiveRecord::Base.configurations case abcs['test']['adapter'] - when /mysql/, /postgresql/, /sqlite/ - ActiveRecord::Tasks::DatabaseTasks.purge abcs['test'] when 'sqlserver' test = abcs.deep_dup['test'] test_database = test['database'] @@ -401,7 +395,7 @@ db_namespace = namespace :db do ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.recreate_database! else - raise "Task not supported by '#{abcs['test']['adapter']}'" + ActiveRecord::Tasks::DatabaseTasks.purge abcs['test'] end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index bcfcb061f2..0995750ecd 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1,4 +1,3 @@ - module ActiveRecord # = Active Record Reflection module Reflection # :nodoc: diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6ec5cf3e18..0053530f73 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -276,7 +276,7 @@ module ActiveRecord stmt.table(table) stmt.key = table[primary_key] - if joins_values.any? + if with_default_scope.joins_values.any? @klass.connection.join_to_update(stmt, arel) else stmt.take(arel.limit) @@ -401,7 +401,7 @@ module ActiveRecord stmt = Arel::DeleteManager.new(arel.engine) stmt.from(table) - if joins_values.any? + if with_default_scope.joins_values.any? @klass.connection.join_to_delete(stmt, arel, table[primary_key]) else stmt.wheres = arel.constraints @@ -474,16 +474,16 @@ module ActiveRecord # Returns sql statement for the relation. # - # Users.where(name: 'Oscar').to_sql + # User.where(name: 'Oscar').to_sql # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar' def to_sql @to_sql ||= klass.connection.to_sql(arel, bind_values.dup) end - # Returns a hash of where conditions + # Returns a hash of where conditions. # - # Users.where(name: 'Oscar').where_values_hash - # # => {name: "oscar"} + # User.where(name: 'Oscar').where_values_hash + # # => {name: "Oscar"} def where_values_hash equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == table_name diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 3f154bd1cc..f10c290755 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -192,7 +192,8 @@ module ActiveRecord def perform_calculation(operation, column_name, options = {}) operation = operation.to_s.downcase - distinct = options[:distinct] + # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count) + distinct = options[:distinct] || self.uniq_value if operation == "count" column_name ||= (select_for_count || :all) diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 59226d316e..eb23e92fb8 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/hash/keys' +require "set" module ActiveRecord class Relation @@ -105,25 +106,26 @@ module ActiveRecord end def merged_wheres - if values[:where] - merged_wheres = relation.where_values + values[:where] - - unless relation.where_values.empty? - # Remove equalities with duplicated left-hand. Last one wins. - seen = {} - merged_wheres = merged_wheres.reverse.reject { |w| - nuke = false - if w.respond_to?(:operator) && w.operator == :== - nuke = seen[w.left] - seen[w.left] = true - end - nuke - }.reverse - end + values[:where] ||= [] - merged_wheres + if values[:where].empty? || relation.where_values.empty? + relation.where_values + values[:where] else - relation.where_values + # Remove equalities from the existing relation with a LHS which is + # present in the relation being merged in. + + seen = Set.new + values[:where].each { |w| + if w.respond_to?(:operator) && w.operator == :== + seen << w.left + end + } + + relation.where_values.reject { |w| + w.respond_to?(:operator) && + w.operator == :== && + seen.include?(w.left) + } + values[:where] end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 83074e72c1..5cd015eba7 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -7,12 +7,12 @@ module ActiveRecord table = default_table if value.is_a?(Hash) - table = Arel::Table.new(column, default_table.engine) - association = klass.reflect_on_association(column.to_sym) - if value.empty? - queries.concat ['1 = 2'] + queries << '1=0' else + table = Arel::Table.new(column, default_table.engine) + association = klass.reflect_on_association(column.to_sym) + value.each do |k, v| queries.concat expand(association && association.klass, table, k, v) end @@ -58,7 +58,7 @@ module ActiveRecord key else key = key.to_s - key.split('.').first.to_sym if key.include?('.') + key.split('.').first if key.include?('.') end end.compact end @@ -66,7 +66,7 @@ module ActiveRecord private def self.build(attribute, value) case value - when Array, ActiveRecord::Associations::CollectionProxy + when Array values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x} ranges, values = values.partition {|v| v.is_a?(Range)} @@ -98,6 +98,11 @@ 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 46c0d6206f..42849d6bc9 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -764,6 +764,11 @@ module ActiveRecord [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts) + + attributes.values.grep(ActiveRecord::Relation) do |rel| + self.bind_values += rel.bind_values + end + PredicateBuilder.build_from_hash(klass, attributes, table) else [opts] diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 2dad1dc177..3c5b871e99 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -33,10 +33,10 @@ module ActiveRecord # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a SET clause. # { name: nil, group_id: 4 } returns "name = NULL , group_id='4'" - def sanitize_sql_for_assignment(assignments) + def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) case assignments when Array; sanitize_sql_array(assignments) - when Hash; sanitize_sql_hash_for_assignment(assignments) + when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) else assignments end end @@ -98,9 +98,9 @@ module ActiveRecord # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. # { status: nil, group_id: 1 } # # => "status = NULL , group_id = 1" - def sanitize_sql_hash_for_assignment(attrs) + def sanitize_sql_hash_for_assignment(attrs, table) attrs.map do |attr, value| - "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}" + "#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value)}" end.join(', ') end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 36bde44e7c..fa9de926c5 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -24,6 +24,7 @@ module ActiveRecord def dump(stream) header(stream) + extensions(stream) tables(stream) trailer(stream) stream @@ -66,6 +67,18 @@ HEADER stream.puts "end" end + def extensions(stream) + return unless @connection.supports_extensions? + extensions = @connection.extensions + if extensions.any? + stream.puts " # These are extensions that must be enabled in order to support this database" + extensions.each do |extension| + stream.puts " enable_extension #{extension.inspect}" + end + stream.puts + end + end + def tables(stream) @connection.tables.sort.each do |tbl| next if ['schema_migrations', ignore_tables].flatten.any? do |ignored| @@ -105,7 +118,7 @@ HEADER # then dump all non-primary key columns column_specs = columns.map do |column| - raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil? + raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type) next if column.name == pk @connection.column_spec(column, @types) end.compact diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 0c3fd1bd29..9746b1c3c2 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -1,4 +1,3 @@ - module ActiveRecord module Scoping extend ActiveSupport::Concern @@ -25,6 +24,5 @@ module ActiveRecord send("#{att}=", value) if respond_to?("#{att}=") end end - end end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 6835d0e01b..5bd481082e 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -1,4 +1,3 @@ - module ActiveRecord module Scoping module Default @@ -99,7 +98,7 @@ module ActiveRecord ) end - self.default_scopes = default_scopes + [scope] + self.default_scopes += [scope] end def build_default_scope # :nodoc: @@ -140,7 +139,6 @@ module ActiveRecord self.ignore_default_scope = false end end - end end end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index cf4cf9e602..a610f479f2 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -42,21 +42,19 @@ module ActiveRecord # # All stored values are automatically available through accessors on the Active Record # object, but sometimes you want to specialize this behavior. This can be done by overwriting - # the default accessors (using the same name as the attribute) and calling - # <tt>read_store_attribute(store_attribute_name, attr_name)</tt> and - # <tt>write_store_attribute(store_attribute_name, attr_name, value)</tt> to actually - # change things. + # the default accessors (using the same name as the attribute) and calling <tt>super</tt> + # to actually change things. # # class Song < ActiveRecord::Base # # Uses a stored integer to hold the volume adjustment of the song # store :settings, accessors: [:volume_adjustment] # # def volume_adjustment=(decibels) - # write_store_attribute(:settings, :volume_adjustment, decibels.to_i) + # super(decibels.to_i) # end # # def volume_adjustment - # read_store_attribute(:settings, :volume_adjustment).to_i + # super.to_i # end # end module Store @@ -75,19 +73,30 @@ module ActiveRecord def store_accessor(store_attribute, *keys) keys = keys.flatten - keys.each do |key| - define_method("#{key}=") do |value| - write_store_attribute(store_attribute, key, value) - end - define_method(key) do - read_store_attribute(store_attribute, key) + _store_accessors_module.module_eval do + keys.each do |key| + define_method("#{key}=") do |value| + write_store_attribute(store_attribute, key, value) + end + + define_method(key) do + read_store_attribute(store_attribute, key) + end end end self.stored_attributes[store_attribute] ||= [] self.stored_attributes[store_attribute] |= keys end + + def _store_accessors_module + @_store_accessors_module ||= begin + mod = Module.new + include mod + mod + end + end end protected diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 67c7e714e6..4fa7cf8a7d 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,6 +1,7 @@ module ActiveRecord module Tasks # :nodoc: class DatabaseAlreadyExists < StandardError; end # :nodoc: + class DatabaseNotSupported < StandardError; end # :nodoc: module DatabaseTasks # :nodoc: extend self @@ -121,6 +122,9 @@ module ActiveRecord def class_for_adapter(adapter) key = @tasks.keys.detect { |pattern| adapter[pattern] } + unless key + raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" + end @tasks[key] end diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index c035ad43a2..e9142481a3 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -60,16 +60,17 @@ module ActiveRecord self.clear_log - self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] + self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] # FIXME: this needs to be refactored so specific database can add their own # ignored SQL, or better yet, use a different notification for the queries # instead examining the SQL content. oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/] - postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im] + postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i] + sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im] - [oracle_ignored, mysql_ignored, postgresql_ignored].each do |db_ignored_sql| + [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql| ignored_sql.concat db_ignored_sql end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 4a608e4f7b..4b7a388dc7 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -286,8 +286,11 @@ module ActiveRecord end # Call the after_commit callbacks + # + # Ensure that it is not called if the object was never persisted (failed create), + # but call it after the commit of a destroyed object def committed! #:nodoc: - run_callbacks :commit + run_callbacks :commit if destroyed? || persisted? ensure clear_transaction_record_state end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 404b492288..f9149c1819 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -164,14 +164,16 @@ module ActiveRecord class AdapterTestWithoutTransaction < ActiveRecord::TestCase self.use_transactional_fixtures = false + class Klass < ActiveRecord::Base + end + def setup - @klass = Class.new(ActiveRecord::Base) - @klass.establish_connection 'arunit' - @connection = @klass.connection + Klass.establish_connection 'arunit' + @connection = Klass.connection end def teardown - @klass.remove_connection + Klass.remove_connection end test "transaction state is reset after a reconnect" do diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index ffd6904aec..b67d70ede7 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -1,6 +1,9 @@ require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase + class Klass < ActiveRecord::Base + end + def setup super @connection = ActiveRecord::Base.connection @@ -17,9 +20,8 @@ class MysqlConnectionTest < ActiveRecord::TestCase run_without_connection do |orig| ar_config = ARTest.connection_config['arunit'] url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" - klass = Class.new(ActiveRecord::Base) - klass.establish_connection(url) - assert_equal ar_config['database'], klass.connection.current_database + Klass.establish_connection(url) + assert_equal ar_config['database'], Klass.connection.current_database end end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 0eb1cc511e..0b017dc4a0 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -16,6 +16,15 @@ module ActiveRecord eosql end + def test_valid_column + column = @conn.column('ex').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end + + def test_invalid_column + assert !@conn.valid_type?(:foobar) + end + def test_client_encoding assert_equal Encoding::UTF_8, @conn.client_encoding end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index fa8f339f00..c03660957e 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -108,7 +108,7 @@ module ActiveRecord @connection.verify! new_connection_pid = @connection.query('select pg_backend_pid()') ensure - raw_connection_class.class_eval <<-CODE + raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1 alias query query_unfake undef query_fake CODE diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 2254be8612..33c796191e 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -3,6 +3,9 @@ require "cases/helper" class PostgresqlArray < ActiveRecord::Base end +class PostgresqlRange < ActiveRecord::Base +end + class PostgresqlTsvector < ActiveRecord::Base end @@ -43,7 +46,106 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase @connection.execute("INSERT INTO postgresql_arrays (id, commission_by_quarter, nicknames) VALUES (1, '{35000,21000,18000,17000}', '{foo,bar,baz}')") @first_array = PostgresqlArray.find(1) + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '[''2012-01-02'', ''2012-01-04'']', + '[0.1, 0.2]', + '[''2010-01-01 14:30'', ''2011-01-01 14:30'']', + '[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']', + '[1, 10]', + '[10, 100]' + ) +_SQL + + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '(''2012-01-02'', ''2012-01-04'')', + '[0.1, 0.2)', + '[''2010-01-01 14:30'', ''2011-01-01 14:30'')', + '[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')', + '(1, 10)', + '(10, 100)' + ) +_SQL + + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '(''2012-01-02'',]', + '[0.1,]', + '[''2010-01-01 14:30'',]', + '[''2010-01-01 14:30:00+05'',]', + '(1,]', + '(10,]' + ) +_SQL + + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '[,]', + '[,]', + '[,]', + '[,]', + '[,]', + '[,]' + ) +_SQL + + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '(''2012-01-02'', ''2012-01-02'')', + '(0.1, 0.1)', + '(''2010-01-01 14:30'', ''2010-01-01 14:30'')', + '(''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')', + '(1, 1)', + '(10, 10)' + ) +_SQL + + if @connection.supports_ranges? + @first_range = PostgresqlRange.find(1) + @second_range = PostgresqlRange.find(2) + @third_range = PostgresqlRange.find(3) + @fourth_range = PostgresqlRange.find(4) + @empty_range = PostgresqlRange.find(5) + end + @connection.execute("INSERT INTO postgresql_tsvectors (id, text_vector) VALUES (1, ' ''text'' ''vector'' ')") + @first_tsvector = PostgresqlTsvector.find(1) @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (1, '567.89'::money)") @@ -82,6 +184,16 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal :text, @first_array.column_for_attribute(:nicknames).type end + def test_data_type_of_range_types + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal :daterange, @first_range.column_for_attribute(:date_range).type + assert_equal :numrange, @first_range.column_for_attribute(:num_range).type + assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type + assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type + assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type + assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type + end + def test_data_type_of_tsvector_types assert_equal :tsvector, @first_tsvector.column_for_attribute(:text_vector).type end @@ -128,11 +240,201 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal "'text' 'vector'", @first_tsvector.text_vector end + def test_int4range_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal 1...11, @first_range.int4_range + assert_equal 2...10, @second_range.int4_range + assert_equal 2...Float::INFINITY, @third_range.int4_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range) + assert_equal nil, @empty_range.int4_range + end + + def test_int8range_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal 10...101, @first_range.int8_range + assert_equal 11...100, @second_range.int8_range + assert_equal 11...Float::INFINITY, @third_range.int8_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range) + assert_equal nil, @empty_range.int8_range + end + + def test_daterange_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range + assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range + assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range) + assert_equal nil, @empty_range.date_range + end + + def test_numrange_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range + assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range + assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range + assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range + assert_equal nil, @empty_range.num_range + end + + def test_tsrange_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + tz = ::ActiveRecord::Base.default_timezone + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Float::INFINITY, @third_range.ts_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) + assert_equal nil, @empty_range.ts_range + end + + def test_tstzrange_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range + assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range + assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Float::INFINITY, @third_range.tstz_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) + assert_equal nil, @empty_range.tstz_range + end + def test_money_values assert_equal 567.89, @first_money.wealth assert_equal(-567.89, @second_money.wealth) end + def test_create_tstzrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') + range = PostgresqlRange.new(:tstz_range => tstzrange) + assert range.save + assert range.reload + assert_equal range.tstz_range, tstzrange + assert_equal range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC') + end + + def test_update_tstzrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET') + assert @first_range.tstz_range = new_tstzrange + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.tstz_range, new_tstzrange + assert @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.tstz_range, nil + end + + def test_create_tsrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + tz = ::ActiveRecord::Base.default_timezone + tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) + range = PostgresqlRange.new(:ts_range => tsrange) + assert range.save + assert range.reload + assert_equal range.ts_range, tsrange + end + + def test_update_tsrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + tz = ::ActiveRecord::Base.default_timezone + new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) + assert @first_range.ts_range = new_tsrange + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.ts_range, new_tsrange + assert @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.ts_range, nil + end + + def test_create_numrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + numrange = BigDecimal.new('0.5')...BigDecimal.new('1') + range = PostgresqlRange.new(:num_range => numrange) + assert range.save + assert range.reload + assert_equal range.num_range, numrange + end + + def test_update_numrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1') + assert @first_range.num_range = new_numrange + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.num_range, new_numrange + assert @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.num_range, nil + end + + def test_create_daterange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + daterange = Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true) + range = PostgresqlRange.new(:date_range => daterange) + assert range.save + assert range.reload + assert_equal range.date_range, daterange + end + + def test_update_daterange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10) + assert @first_range.date_range = new_daterange + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.date_range, new_daterange + assert @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.date_range, nil + end + + def test_create_int4range + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + int4range = Range.new(3, 50, true) + range = PostgresqlRange.new(:int4_range => int4range) + assert range.save + assert range.reload + assert_equal range.int4_range, int4range + end + + def test_update_int4range + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_int4range = 6...10 + assert @first_range.int4_range = new_int4range + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.int4_range, new_int4range + assert @first_range.int4_range = 3...3 + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.int4_range, nil + end + + def test_create_int8range + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + int8range = Range.new(30, 50, true) + range = PostgresqlRange.new(:int8_range => int8range) + assert range.save + assert range.reload + assert_equal range.int8_range, int8range + end + + def test_update_int8range + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_int8range = 60000...10000000 + assert @first_range.int8_range = new_int8range + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.int8_range, new_int8range + assert @first_range.int8_range = 39999...39999 + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.int8_range, nil + end + def test_update_tsvector new_text_vector = "'new' 'text' 'vector'" assert @first_tsvector.text_vector = new_text_vector @@ -243,13 +545,13 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase def test_update_bit_string new_bit_string = '11111111' - new_bit_string_varying = 'FF' + new_bit_string_varying = '11111110' assert @first_bit_string.bit_string = new_bit_string assert @first_bit_string.bit_string_varying = new_bit_string_varying assert @first_bit_string.save assert @first_bit_string.reload - assert_equal @first_bit_string.bit_string, new_bit_string - assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying + assert_equal new_bit_string, @first_bit_string.bit_string + assert_equal new_bit_string_varying, @first_bit_string.bit_string_varying end def test_update_oid diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 23bafde17b..6640f9b497 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -11,15 +11,23 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection - begin - @connection.transaction do - @connection.create_table('hstores') do |t| - t.hstore 'tags', :default => '' - end - end - rescue ActiveRecord::StatementInvalid + + unless @connection.supports_extensions? return skip "do not test on PG without hstore" end + + unless @connection.extension_enabled?('hstore') + @connection.enable_extension 'hstore' + @connection.commit_db_transaction + end + + @connection.reconnect! + + @connection.transaction do + @connection.create_table('hstores') do |t| + t.hstore 'tags', :default => '' + end + end @column = Hstore.columns.find { |c| c.name == 'tags' } end @@ -27,6 +35,32 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase @connection.execute 'drop table if exists hstores' end + def test_hstore_included_in_extensions + assert @connection.respond_to?(:extensions), "connection should have a list of extensions" + assert @connection.extensions.include?('hstore'), "extension list should include hstore" + end + + def test_hstore_enabled + assert @connection.extension_enabled?('hstore') + end + + def test_disable_hstore + if @connection.extension_enabled?('hstore') + @connection.disable_extension 'hstore' + assert_not @connection.extension_enabled?('hstore') + end + end + + def test_enable_hstore + if @connection.extension_enabled?('hstore') + @connection.disable_extension 'hstore' + end + + assert_not @connection.extension_enabled?('hstore') + @connection.enable_extension 'hstore' + assert @connection.extension_enabled?('hstore') + end + def test_column assert_equal :hstore, @column.type end diff --git a/activerecord/test/cases/adapters/postgresql/intrange_test.rb b/activerecord/test/cases/adapters/postgresql/intrange_test.rb deleted file mode 100644 index 5f6a64619d..0000000000 --- a/activerecord/test/cases/adapters/postgresql/intrange_test.rb +++ /dev/null @@ -1,106 +0,0 @@ -# encoding: utf-8 - -require "cases/helper" -require 'active_record/base' -require 'active_record/connection_adapters/postgresql_adapter' - -class PostgresqlIntrangesTest < ActiveRecord::TestCase - class IntRangeDataType < ActiveRecord::Base - self.table_name = 'intrange_data_type' - end - - def setup - @connection = ActiveRecord::Base.connection - begin - @connection.transaction do - @connection.create_table('intrange_data_type') do |t| - t.intrange 'int_range', :default => (1..10) - t.intrange 'long_int_range', :limit => 8, :default => (1..100) - end - end - rescue ActiveRecord::StatementInvalid - return skip "do not test on PG without ranges" - end - @int_range_column = IntRangeDataType.columns.find { |c| c.name == 'int_range' } - @long_int_range_column = IntRangeDataType.columns.find { |c| c.name == 'long_int_range' } - end - - def teardown - @connection.execute 'drop table if exists intrange_data_type' - end - - def test_columns - assert_equal :intrange, @int_range_column.type - assert_equal :intrange, @long_int_range_column.type - end - - def test_type_cast_intrange - assert @int_range_column - assert_equal(true, @int_range_column.has_default?) - assert_equal((1..10), @int_range_column.default) - assert_equal("int4range", @int_range_column.sql_type) - - data = "[1,10)" - hash = @int_range_column.class.string_to_intrange data - assert_equal((1..9), hash) - assert_equal((1..9), @int_range_column.type_cast(data)) - - assert_equal((nil..nil), @int_range_column.type_cast("empty")) - assert_equal((1..5), @int_range_column.type_cast('[1,5]')) - assert_equal((2..4), @int_range_column.type_cast('(1,5)')) - assert_equal((2..39), @int_range_column.type_cast('[2,40)')) - assert_equal((10..20), @int_range_column.type_cast('(9,20]')) - end - - def test_type_cast_long_intrange - assert @long_int_range_column - assert_equal(true, @long_int_range_column.has_default?) - assert_equal((1..100), @long_int_range_column.default) - assert_equal("int8range", @long_int_range_column.sql_type) - end - - def test_rewrite - @connection.execute "insert into intrange_data_type (int_range) VALUES ('(1, 6)')" - x = IntRangeDataType.first - x.int_range = (1..100) - assert x.save! - end - - def test_select - @connection.execute "insert into intrange_data_type (int_range) VALUES ('(1, 4]')" - x = IntRangeDataType.first - assert_equal((2..4), x.int_range) - end - - def test_empty_range - @connection.execute %q|insert into intrange_data_type (int_range) VALUES('empty')| - x = IntRangeDataType.first - assert_equal((nil..nil), x.int_range) - end - - def test_rewrite_to_nil - @connection.execute %q|insert into intrange_data_type (int_range) VALUES('(1, 4]')| - x = IntRangeDataType.first - x.int_range = nil - assert x.save! - assert_equal(nil, x.int_range) - end - - def test_invalid_intrange - assert IntRangeDataType.create!(int_range: ('a'..'d')) - x = IntRangeDataType.first - assert_equal(nil, x.int_range) - end - - def test_save_empty_range - assert IntRangeDataType.create!(int_range: (nil..nil)) - x = IntRangeDataType.first - assert_equal((nil..nil), x.int_range) - end - - def test_save_invalid_data - assert_raises(ActiveRecord::StatementInvalid) do - IntRangeDataType.create!(int_range: "empty1") - end - end -end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 872204c644..d33e360bd4 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -10,6 +10,15 @@ module ActiveRecord @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') end + def test_valid_column + column = @connection.column('ex').find { |col| col.name == 'id' } + assert @connection.valid_type?(column.type) + end + + def test_invalid_column + assert !@connection.valid_type?(:foobar) + end + def test_primary_key assert_equal 'id', @connection.primary_key('ex') end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 2ba9143cd5..a7b2764fc1 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -17,7 +17,7 @@ module ActiveRecord @conn.extend(Module.new { def logger; end }) column = Struct.new(:type, :name).new(:string, "foo") binary = SecureRandom.hex - expected = binary.dup.encode!('utf-8') + expected = binary.dup.encode!(Encoding::UTF_8) assert_equal expected, @conn.type_cast(binary, column) end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 003052bac4..64af49acec 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -25,6 +25,15 @@ module ActiveRecord @conn.intercepted = true end + def test_valid_column + column = @conn.column('items').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end + + def test_invalid_column + assert @conn.valid_type?(:foobar) + end + def teardown @conn.intercepted = false @conn.logged = [] diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index aa5b27623e..244e0b7179 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -12,6 +12,7 @@ if ActiveRecord::Base.connection.supports_migrations? def teardown @connection.drop_table :fruits rescue nil + ActiveRecord::SchemaMigration.delete_all rescue nil end def test_schema_define diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index be2d30641e..3bf9125013 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1162,4 +1162,10 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.where('1 = 0').scoping { Comment.preload(:post).find(1).post } ) end + + test "preloading does not cache has many association subset when preloaded with a through association" do + author = Author.includes(:comments_with_order_and_conditions, :posts).first + 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 end diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb deleted file mode 100644 index fe2b82f2c1..0000000000 --- a/activerecord/test/cases/associations/habtm_join_table_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'cases/helper' - -class MyReader < ActiveRecord::Base - has_and_belongs_to_many :my_books -end - -class MyBook < ActiveRecord::Base - has_and_belongs_to_many :my_readers -end - -class HabtmJoinTableTest < ActiveRecord::TestCase - def setup - ActiveRecord::Base.connection.create_table :my_books, :force => true do |t| - t.string :name - end - assert ActiveRecord::Base.connection.table_exists?(:my_books) - - ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t| - t.string :name - end - assert ActiveRecord::Base.connection.table_exists?(:my_readers) - - ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t| - t.integer :my_book_id - t.integer :my_reader_id - end - assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers) - end - - def teardown - ActiveRecord::Base.connection.drop_table :my_books - ActiveRecord::Base.connection.drop_table :my_readers - ActiveRecord::Base.connection.drop_table :my_books_my_readers - 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 7e6c7d5862..fd6d531645 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -19,6 +19,7 @@ require 'models/line_item' require 'models/car' require 'models/bulb' require 'models/engine' +require 'models/categorization' class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase class Invoice < ActiveRecord::Base @@ -108,7 +109,8 @@ end class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, - :people, :posts, :readers, :taggings, :cars, :essays + :people, :posts, :readers, :taggings, :cars, :essays, + :categorizations def setup Client.destroyed_client_ids.clear @@ -625,6 +627,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 3, company.clients_of_firm.size end + def test_collection_not_empty_after_building + company = companies(:first_firm) + assert_predicate company.contracts, :empty? + company.contracts.build + assert_not_predicate company.contracts, :empty? + end + def test_collection_size_twice_for_regressions post = posts(:thinking) assert_equal 0, post.readers.size @@ -1705,4 +1714,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, post.comments.count end end + + test "collection proxy respects default scope" do + author = authors(:mary) + assert !author.first_posts.exists? + end + + test "association with extend option" do + post = posts(:welcome) + assert_equal "lifo", post.comments_with_extend.author + assert_equal "hello", post.comments_with_extend.greeting + end + + test "association with extend option with multiple extensions" do + post = posts(:welcome) + assert_equal "lifo", post.comments_with_extend_2.author + assert_equal "hello", post.comments_with_extend_2.greeting + end + + test "delete record with complex joins" do + david = authors(:david) + + post = david.posts.first + post.type = 'PostWithSpecialCategorization' + post.save + + categorization = post.categorizations.first + categorization.special = true + categorization.save + + assert_not_equal [], david.posts_with_special_categorizations + david.posts_with_special_categorizations = [] + assert_equal [], david.posts_with_special_categorizations + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index af91fb2920..67d18f313a 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -894,4 +894,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + test "has many through with default scope on the target" do + person = people(:michael) + assert_equal [posts(:thinking)], person.first_posts + + readers(:michael_authorless).update(first_post_id: 1) + assert_equal [posts(:thinking)], person.reload.first_posts + end end diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 03d99d19f6..e355ed3495 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -221,6 +221,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload_via_joins + # preload table schemas + Author.joins(:post_categories).first + assert_includes_and_joins_equal( Author.where('categories.id' => categories(:cooking).id), [authors(:bob)], :post_categories @@ -246,6 +249,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload_via_joins + # preload table schemas + Category.joins(:post_comments).first + assert_includes_and_joins_equal( Category.where('comments.id' => comments(:more_greetings).id).order('categories.id'), [categories(:general), categories(:technology)], :post_comments @@ -271,6 +277,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload_via_joins + # preload table schemas + Author.joins(:category_post_comments).first + assert_includes_and_joins_equal( Author.where('comments.id' => comments(:does_it_hurt).id).order('authors.id'), [authors(:david), authors(:mary)], :category_post_comments diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb index 25d2896ab0..9a486cf8b8 100644 --- a/activerecord/test/cases/binary_test.rb +++ b/activerecord/test/cases/binary_test.rb @@ -23,7 +23,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) # Mysql adapter doesn't properly encode things, so we have to do it if current_adapter?(:MysqlAdapter) - name.force_encoding('UTF-8') + name.force_encoding(Encoding::UTF_8) end assert_equal 'いただきます!', name end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index b7622705bf..be49e948fc 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -341,6 +341,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 5, Account.count(:firm_id) end + def test_count_with_uniq + assert_equal 4, Account.select(:credit_limit).uniq.count + end + def test_count_with_column_and_options_parameter assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id) end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 0718d0886f..23e64bee7e 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -327,6 +327,17 @@ module ActiveRecord def test_pool_sets_connection_visitor assert @pool.connection.visitor.is_a?(Arel::Visitors::ToSql) end + + # make sure exceptions are thrown when establish_connection + # is called with a anonymous class + def test_anonymous_class_exception + anonymous = Class.new(ActiveRecord::Base) + handler = ActiveRecord::Base.connection_handler + + assert_raises(RuntimeError) { + handler.establish_connection anonymous, nil + } + end end end end diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 52de0efe7f..f0a2cdca1a 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -8,40 +8,66 @@ module ActiveRecord Resolver.new(spec, {}).spec.config end + def test_url_invalid_adapter + assert_raises(LoadError) do + resolve 'ridiculous://foo?encoding=utf8' + end + end + + # The abstract adapter is used simply to bypass the bit of code that + # checks that the adapter file can be required in. + def test_url_host_no_db - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) - spec = resolve 'mysql://foo?encoding=utf8' + spec = resolve 'abstract://foo?encoding=utf8' assert_equal({ - :adapter => "mysql", + :adapter => "abstract", :host => "foo", :encoding => "utf8" }, spec) end def test_url_host_db - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) - spec = resolve 'mysql://foo/bar?encoding=utf8' + spec = resolve 'abstract://foo/bar?encoding=utf8' assert_equal({ - :adapter => "mysql", + :adapter => "abstract", :database => "bar", :host => "foo", :encoding => "utf8" }, spec) end def test_url_port - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) - spec = resolve 'mysql://foo:123?encoding=utf8' + spec = resolve 'abstract://foo:123?encoding=utf8' assert_equal({ - :adapter => "mysql", + :adapter => "abstract", :port => 123, :host => "foo", :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 - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) password = 'am@z1ng_p@ssw0rd#!' encoded_password = URI.encode_www_form_component(password) - spec = resolve "mysql://foo:#{encoded_password}@localhost/bar" + spec = resolve "abstract://foo:#{encoded_password}@localhost/bar" assert_equal password, spec[:password] end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index b9961a4420..c7d2ba6073 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -79,6 +79,8 @@ class DirtyTest < ActiveRecord::TestCase assert pirate.created_on_changed? assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was assert_equal old_created_on, pirate.created_on_was + pirate.created_on = old_created_on + assert !pirate.created_on_changed? end end @@ -551,18 +553,17 @@ class DirtyTest < ActiveRecord::TestCase end end - def test_setting_time_attributes_with_time_zone_field_to_same_time_should_not_be_marked_as_a_change + def test_datetime_attribute_can_be_updated_with_fractional_seconds in_time_zone 'Paris' do target = Class.new(ActiveRecord::Base) - target.table_name = 'pirates' + target.table_name = 'topics' - created_on = Time.now + written_on = Time.utc(2012, 12, 1, 12, 0, 0).in_time_zone('Paris') - pirate = target.create(:created_on => created_on) - pirate.reload # Here mysql truncate the usec value to 0 + topic = target.create(:written_on => written_on) + topic.written_on += 0.3 - pirate.created_on = created_on - assert !pirate.created_on_changed? + assert topic.written_on_changed?, 'Fractional second update not detected' end end diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 4e2adff344..eca500f7e4 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -108,18 +108,20 @@ module ActiveRecord end def test_dup_validity_is_independent - Topic.validates_presence_of :title - topic = Topic.new("title" => "Litterature") - topic.valid? - - duped = topic.dup - duped.title = nil - assert duped.invalid? - - topic.title = nil - duped.title = 'Mathematics' - assert topic.invalid? - assert duped.valid? + repair_validations(Topic) do + Topic.validates_presence_of :title + topic = Topic.new("title" => "Litterature") + topic.valid? + + duped = topic.dup + duped.title = nil + assert duped.invalid? + + topic.title = nil + duped.title = 'Mathematics' + assert topic.invalid? + assert duped.valid? + end end end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 5ffb32e809..7dbb6616f8 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -15,6 +15,8 @@ require 'support/connection' # TODO: Move all these random hacks into the ARTest namespace and into the support/ dir +Thread.abort_on_exception = true + # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index 5ac4a16f33..cad759bba9 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -35,7 +35,7 @@ module ActiveRecord t.column :foo, :string end - assert_equal %w(foo id), connection.columns(:testings).map(&:name).sort + assert_equal %w(id foo), connection.columns(:testings).map(&:name) end def test_create_table_with_not_null_column @@ -119,7 +119,7 @@ module ActiveRecord t.column :foo, :string end - assert_equal %w(foo testing_id), connection.columns(:testings).map(&:name).sort + assert_equal %w(testing_id foo), connection.columns(:testings).map(&:name) end def test_create_table_with_primary_key_prefix_as_table_name @@ -129,7 +129,7 @@ module ActiveRecord t.column :foo, :string end - assert_equal %w(foo testingid), connection.columns(:testings).map(&:name).sort + assert_equal %w(testingid foo), connection.columns(:testings).map(&:name) end def test_create_table_raises_when_redefining_primary_key_column diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 187c6e8447..fa8dec0e15 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -30,9 +30,13 @@ class MigrationTest < ActiveRecord::TestCase Reminder.reset_column_information ActiveRecord::Migration.verbose = true ActiveRecord::Migration.message_count = 0 + ActiveRecord::Base.connection.schema_cache.clear! end def teardown + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + ActiveRecord::Base.connection.initialize_schema_migrations_table ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" @@ -44,6 +48,7 @@ class MigrationTest < ActiveRecord::TestCase %w(reminders people_reminders prefix_reminders_suffix).each do |table| Reminder.connection.drop_table(table) rescue nil end + Reminder.reset_table_name Reminder.reset_column_information %w(last_name key bio age height wealth birthday favorite_day @@ -257,9 +262,6 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Base.table_name_suffix = "" Reminder.reset_table_name assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name - ensure - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" end def test_proper_table_name @@ -286,9 +288,6 @@ class MigrationTest < ActiveRecord::TestCase Reminder.reset_table_name assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table) - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - Reminder.reset_table_name end def test_rename_table_with_prefix_and_suffix @@ -307,8 +306,6 @@ class MigrationTest < ActiveRecord::TestCase assert_equal "hello world", Thing.first.content ensure - ActiveRecord::Base.table_name_prefix = '' - ActiveRecord::Base.table_name_suffix = '' Thing.reset_table_name Thing.reset_sequence_name end @@ -326,9 +323,6 @@ class MigrationTest < ActiveRecord::TestCase WeNeedReminders.down assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } ensure - ActiveRecord::Base.table_name_prefix = '' - ActiveRecord::Base.table_name_suffix = '' - Reminder.reset_table_name Reminder.reset_sequence_name end @@ -437,6 +431,8 @@ if ActiveRecord::Base.connection.supports_bulk_alter? def setup @connection = Person.connection @connection.create_table(:delete_me, :force => true) {|t| } + Person.reset_column_information + Person.reset_sequence_name end def teardown diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index b936cca875..08dbf19e7b 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -399,6 +399,12 @@ class PersistencesTest < ActiveRecord::TestCase assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') } end + def test_string_ids + mv = Minivan.where(:minivan_id => 1234).first_or_initialize + assert mv.new_record? + assert_equal '1234', mv.minivan_id + end + def test_update_attribute_with_one_updated t = Topic.first t.update_attribute(:title, 'super_title') diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 3dd11ae89d..0ad05223d4 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, Object.new) + assert_equal float.to_s, @quoter.quote(float, FakeColumn.new(:float)) end def test_quote_fixnum fixnum = 1 assert_equal fixnum.to_s, @quoter.quote(fixnum, nil) - assert_equal fixnum.to_s, @quoter.quote(fixnum, Object.new) + assert_equal fixnum.to_s, @quoter.quote(fixnum, FakeColumn.new(:integer)) 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, Object.new) + assert_equal bignum.to_s, @quoter.quote(bignum, FakeColumn.new(:integer)) 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, Object.new) + assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, FakeColumn.new(:decimal)) 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, Object.new) + assert_equal "'lol'", @quoter.quote(Date.today, FakeColumn.new(:date)) assert_equal "'lol'", @quoter.quote(Time.now, nil) - assert_equal "'lol'", @quoter.quote(Time.now, Object.new) + assert_equal "'lol'", @quoter.quote(Time.now, FakeColumn.new(:time)) assert_equal "'lol'", @quoter.quote(DateTime.now, nil) - assert_equal "'lol'", @quoter.quote(DateTime.now, Object.new) + assert_equal "'lol'", @quoter.quote(DateTime.now, FakeColumn.new(:datetime)) end def test_crazy_object diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index f69a248491..53cdf89b1f 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -8,7 +8,20 @@ require 'models/edge' module ActiveRecord class WhereTest < ActiveRecord::TestCase - fixtures :posts, :edges + fixtures :posts, :edges, :authors + + def test_where_copies_bind_params + author = authors(:david) + posts = author.posts.where('posts.id != 1') + joined = Post.where(id: posts) + + assert_operator joined.length, :>, 0 + + joined.each { |post| + assert_equal author, post.author + assert_not_equal 1, post.id + } + end def test_belongs_to_shallow_where author = Author.new @@ -95,5 +108,30 @@ 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 78fb91d321..8e6c38706f 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -161,6 +161,28 @@ class RelationScopingTest < ActiveRecord::TestCase assert !Developer.all.where_values.include?("name = 'Jamis'") end + + def test_default_scope_filters_on_joins + assert_equal 1, DeveloperFilteredOnJoins.all.count + assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins) + end + + def test_update_all_default_scope_filters_on_joins + DeveloperFilteredOnJoins.update_all(:salary => 65000) + assert_equal 65000, Developer.find(developers(:david).id).salary + + # has not changed jamis + assert_not_equal 65000, Developer.find(developers(:jamis).id).salary + end + + def test_delete_all_default_scope_filters_on_joins + assert_not_equal [], DeveloperFilteredOnJoins.all + + DeveloperFilteredOnJoins.delete_all() + + assert_equal [], DeveloperFilteredOnJoins.all + assert_not_equal [], Developer.all + end end class NestedRelationScopingTest < ActiveRecord::TestCase @@ -369,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 50000, wheres[:salary] + assert_equal Arel.sql("50000"), wheres[:salary] end def test_default_scope_with_module_includes wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal 50000, wheres[:salary] + assert_equal Arel.sql("50000"), wheres[:salary] end def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal 50000, wheres[:salary] + assert_equal Arel.sql("50000"), wheres[:salary] end def test_scope_overwrites_default diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 3a499a2025..379c0c0758 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -404,6 +404,13 @@ class RelationTest < ActiveRecord::TestCase end end + def test_preload_applies_to_all_chained_preloaded_scopes + assert_queries(3) do + post = Post.with_comments.with_tags.first + assert post + end + end + def test_find_with_included_associations assert_queries(2) do posts = Post.includes(:comments).order('posts.id') @@ -1481,4 +1488,17 @@ class RelationTest < ActiveRecord::TestCase Array.send(:remove_method, :__omg__) end end + + test "merge collapses wheres from the LHS only" do + left = Post.where(title: "omg").where(comments_count: 1) + right = Post.where(title: "wtf").where(title: "bbq") + + expected = [left.where_values[1]] + right.where_values + merged = left.merge(right) + + assert_equal expected, merged.where_values + assert !merged.to_sql.include?("omg") + assert merged.to_sql.include?("wtf") + assert merged.to_sql.include?("bbq") + end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index cae12e0e3a..bfecc0d1e9 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -1,6 +1,5 @@ require "cases/helper" - class SchemaDumperTest < ActiveRecord::TestCase def setup super @@ -231,6 +230,21 @@ class SchemaDumperTest < ActiveRecord::TestCase end if current_adapter?(:PostgreSQLAdapter) + def test_schema_dump_includes_extensions + connection = ActiveRecord::Base.connection + skip unless connection.supports_extensions? + + connection.stubs(:extensions).returns(['hstore']) + output = standard_dump + assert_match "# These are extensions that must be enabled", output + assert_match %r{enable_extension "hstore"}, output + + connection.stubs(:extensions).returns([]) + output = standard_dump + assert_no_match "# These are extensions that must be enabled", output + assert_no_match %r{enable_extension}, output + end + def test_schema_dump_includes_xml_shorthand_definition output = standard_dump if %r{create_table "postgresql_xml_data_type"} =~ output diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 43bf285ba9..3e32d866ee 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -35,6 +35,12 @@ class StoreTest < ActiveRecord::TestCase assert_equal '(123) 456-7890', @john.phone_number end + test "overriding a read accessor using super" do + @john.settings[:color] = nil + + assert_equal 'red', @john.color + end + test "updating the store will mark it as changed" do @john.color = 'red' assert @john.settings_changed? @@ -66,6 +72,12 @@ class StoreTest < ActiveRecord::TestCase assert_equal '1234567890', @john.settings[:phone_number] end + test "overriding a write accessor using super" do + @john.color = 'yellow' + + assert_equal 'blue', @john.color + end + test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do @john.json_data = ActiveSupport::HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy') @john.height = 'low' diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 659d5eae72..3bfbc92afd 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -31,6 +31,12 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz) ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :foo}, "awesome-file.sql") end + + def test_unregistered_task + assert_raise(ActiveRecord::Tasks::DatabaseNotSupported) do + ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :bar}, "awesome-file.sql") + end + end end class DatabaseTasksCreateTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 869892e33f..fd5651b4e0 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -5,9 +5,29 @@ class TransactionCallbacksTest < ActiveRecord::TestCase self.use_transactional_fixtures = false fixtures :topics + class ReplyWithCallbacks < ActiveRecord::Base + self.table_name = :topics + + belongs_to :topic, foreign_key: "parent_id" + + validates_presence_of :content + + after_commit :do_after_commit, on: :create + + def history + @history ||= [] + end + + def do_after_commit + history << :commit_on_create + end + end + class TopicWithCallbacks < ActiveRecord::Base self.table_name = :topics + has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id" + after_commit{|record| record.send(:do_after_commit, nil)} after_commit(:on => :create){|record| record.send(:do_after_commit, :create)} after_commit(:on => :update){|record| record.send(:do_after_commit, :update)} @@ -93,6 +113,13 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal [:commit_on_create], @new_record.history end + def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record_if_create_succeeds_creating_through_association + topic = TopicWithCallbacks.create!(:title => "New topic", :written_on => Date.today) + reply = topic.replies.create + + assert_equal [], reply.history + end + def test_call_after_rollback_after_transaction_rollsback @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index bcbc48b38a..546737b398 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -451,6 +451,34 @@ class TransactionTest < ActiveRecord::TestCase end end + def test_transactions_state_from_rollback + connection = Topic.connection + transaction = ActiveRecord::ConnectionAdapters::ClosedTransaction.new(connection).begin + + assert transaction.open? + assert !transaction.state.rolledback? + assert !transaction.state.committed? + + transaction.perform_rollback + + assert transaction.state.rolledback? + assert !transaction.state.committed? + end + + def test_transactions_state_from_commit + connection = Topic.connection + transaction = ActiveRecord::ConnectionAdapters::ClosedTransaction.new(connection).begin + + assert transaction.open? + assert !transaction.state.rolledback? + assert !transaction.state.committed? + + transaction.perform_commit + + assert !transaction.state.rolledback? + assert transaction.state.committed? + end + private %w(validation save destroy).each do |filter| diff --git a/activerecord/test/fixtures/readers.yml b/activerecord/test/fixtures/readers.yml index 8a6076655b..14b883f041 100644 --- a/activerecord/test/fixtures/readers.yml +++ b/activerecord/test/fixtures/readers.yml @@ -2,8 +2,10 @@ michael_welcome: id: 1 post_id: 1 person_id: 1 + first_post_id: 2 michael_authorless: id: 2 post_id: 3 - person_id: 1
\ No newline at end of file + person_id: 1 + first_post_id: 3 diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index 467f3ccd39..4c3b71e8f9 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -1,10 +1,24 @@ class Admin::User < ActiveRecord::Base + class Coder + def initialize(default = {}) + @default = default + end + + def dump(o) + ActiveSupport::JSON.encode(o || @default) + end + + def load(s) + s.present? ? ActiveSupport::JSON.decode(s) : @default.clone + end + end + belongs_to :account store :settings, :accessors => [ :color, :homepage ] store_accessor :settings, :favorite_food store :preferences, :accessors => [ :remember_login ] - store :json_data, :accessors => [ :height, :weight ], :coder => JSON - store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => JSON + store :json_data, :accessors => [ :height, :weight ], :coder => Coder.new + store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => Coder.new def phone_number read_store_attribute(:settings, :phone_number).gsub(/(\d{3})(\d{3})(\d{4})/,'(\1) \2-\3') @@ -13,4 +27,13 @@ class Admin::User < ActiveRecord::Base def phone_number=(value) write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/,'')) end + + def color + super || 'red' + end + + def color=(value) + value = 'blue' unless %w(black red green blue).include?(value) + super + end end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 6935cfb0ea..8423411474 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -9,17 +9,8 @@ class Author < ActiveRecord::Base has_many :posts_with_categories, -> { includes(:categories) }, :class_name => "Post" has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, :class_name => "Post" has_many :posts_containing_the_letter_a, :class_name => "Post" - has_many :posts_with_extension, :class_name => "Post" do #, :extend => ProxyTestExtension - def testing_proxy_owner - proxy_owner - end - def testing_proxy_reflection - proxy_reflection - end - def testing_proxy_target - proxy_target - end - end + has_many :posts_with_special_categorizations, :class_name => 'PostWithSpecialCategorization' + has_many :posts_with_extension, :class_name => "Post" has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, :class_name => 'Post' has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, :class_name => 'Post' has_many :comments, :through => :posts diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index f8c8ebb70c..7da39a8e33 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -31,9 +31,4 @@ class Category < ActiveRecord::Base end class SpecialCategory < Category - - def self.what_are_you - 'a special category...' - end - end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 4b2015fe01..ede5fbd0c6 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -29,16 +29,10 @@ class Comment < ActiveRecord::Base end class SpecialComment < Comment - def self.what_are_you - 'a special comment...' - end end class SubSpecialComment < SpecialComment end class VerySpecialComment < Comment - def self.what_are_you - 'a very special comment...' - end end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 683cb54a10..81bc87bd42 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -101,6 +101,15 @@ class DeveloperWithIncludes < ActiveRecord::Base default_scope { includes(:audit_logs) } end +class DeveloperFilteredOnJoins < ActiveRecord::Base + self.table_name = 'developers' + has_and_belongs_to_many :projects, -> { order('projects.id') }, :foreign_key => 'developer_id', :join_table => 'developers_projects' + + def self.default_scope + joins(:projects).where(:projects => { :name => 'Active Controller' }) + end +end + class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' default_scope { order('salary DESC') } diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index c602ca5eac..fa717ef8d6 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -15,6 +15,7 @@ class Person < ActiveRecord::Base has_many :fixed_bad_references, -> { where :favourite => true }, :class_name => 'BadReference' has_one :favourite_reference, -> { where 'favourite=?', true }, :class_name => 'Reference' has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order('comments.id') }, :through => :readers, :source => :post + has_many :first_posts, -> { where(id: [1, 2]) }, through: :readers has_many :jobs, :through => :references has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index c995f59a15..93a7a2073c 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -5,16 +5,18 @@ class Post < ActiveRecord::Base end end + module NamedExtension2 + def greeting + "hello" + end + end + scope :containing_the_letter_a, -> { where("body LIKE '%a%'") } scope :ranked_by_comments, -> { order("comments_count DESC") } scope :limit_by, lambda {|l| limit(l) } - belongs_to :author do - def greeting - "hello" - end - end + belongs_to :author belongs_to :author_with_posts, -> { includes(:posts) }, :class_name => "Author", :foreign_key => :author_id belongs_to :author_with_address, -> { includes(:author_address) }, :class_name => "Author", :foreign_key => :author_id @@ -29,6 +31,9 @@ class Post < ActiveRecord::Base scope :with_very_special_comments, -> { joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) } scope :with_post, ->(post_id) { joins(:comments).where(:comments => { :post_id => post_id }) } + scope :with_comments, -> { preload(:comments) } + scope :with_tags, -> { preload(:taggings) } + has_many :comments do def find_most_recent order("id DESC").first @@ -43,6 +48,14 @@ class Post < ActiveRecord::Base end end + has_many :comments_with_extend, extend: NamedExtension, class_name: "Comment", foreign_key: "post_id" do + def greeting + "hello" + end + end + + has_many :comments_with_extend_2, extend: [NamedExtension, NamedExtension2], class_name: "Comment", foreign_key: "post_id" + has_many :author_favorites, :through => :author has_many :author_categorizations, :through => :author, :source => :categorizations has_many :author_addresses, :through => :author @@ -151,18 +164,6 @@ class SubStiPost < StiPost self.table_name = Post.table_name end -ActiveSupport::Deprecation.silence do - class DeprecatedPostWithComment < ActiveRecord::Base - self.table_name = 'posts' - default_scope where("posts.comments_count > 0").order("posts.comments_count ASC") - end -end - -class PostForAuthor < ActiveRecord::Base - self.table_name = 'posts' - cattr_accessor :selected_author -end - class FirstPost < ActiveRecord::Base self.table_name = 'posts' default_scope { where(:id => 1) } @@ -177,6 +178,11 @@ class PostWithDefaultInclude < ActiveRecord::Base has_many :comments, :foreign_key => :post_id end +class PostWithSpecialCategorization < Post + has_many :categorizations, :foreign_key => :post_id + default_scope { where(:type => 'PostWithSpecialCategorization').joins(:categorizations).where(:categorizations => { :special => true }) } +end + class PostWithDefaultScope < ActiveRecord::Base self.table_name = 'posts' default_scope { order(:title) } diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index af3ec4be83..90273adafc 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -40,7 +40,4 @@ class Project < ActiveRecord::Base end class SpecialProject < Project - def hello_world - "hello there!" - end end diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb index f8fb9c573e..3a6b7fad34 100644 --- a/activerecord/test/models/reader.rb +++ b/activerecord/test/models/reader.rb @@ -2,6 +2,7 @@ class Reader < ActiveRecord::Base belongs_to :post belongs_to :person, :inverse_of => :readers belongs_to :single_person, :class_name => 'Person', :foreign_key => :person_id, :inverse_of => :reader + belongs_to :first_post, -> { where(id: [2, 3]) } end class SecureReader < ActiveRecord::Base diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index f7f4cebc5a..17035bf338 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -108,6 +108,7 @@ class ImportantTopic < Topic end class BlankTopic < Topic + # declared here to make sure that dynamic finder with a bang can find a model that responds to `blank?` def blank? true end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index ae13f2cd8a..83b50030bd 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,7 +1,7 @@ ActiveRecord::Schema.define do - %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees - postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_intrange_data_type).each do |table_name| + %w(postgresql_ranges postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees + postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -73,6 +73,18 @@ _SQL ); _SQL + execute <<_SQL if supports_ranges? + CREATE TABLE postgresql_ranges ( + id SERIAL PRIMARY KEY, + date_range daterange, + num_range numrange, + ts_range tsrange, + tstz_range tstzrange, + int4_range int4range, + int8_range int8range + ); +_SQL + execute <<_SQL CREATE TABLE postgresql_tsvectors ( id SERIAL PRIMARY KEY, @@ -106,16 +118,6 @@ _SQL ); _SQL end - - if 't' == select_value("select 'int4range'=ANY(select typname from pg_type)") - execute <<_SQL - CREATE TABLE postgresql_intrange_data_type ( - id SERIAL PRIMARY KEY, - int_range int4range, - int_long_range int8range - ); -_SQL - end execute <<_SQL CREATE TABLE postgresql_moneys ( diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 46219c53db..d789b6cb7a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -540,6 +540,8 @@ 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 @@ -567,6 +569,7 @@ ActiveRecord::Schema.define do t.integer :post_id, :null => false t.integer :person_id, :null => false t.boolean :skimmer, :default => false + t.integer :first_post_id end create_table :references, :force => true do |t| diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 2c0d1de70f..5f7559b5a6 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,24 @@ ## Rails 4.0.0 (unreleased) ## +* 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 + Firefox. + + *James Harton* + +* Improve `String#squish` to handle Unicode whitespace. *Antoine Lyset* + +* Standardise on `to_time` returning an instance of `Time` in the local system timezone + across `String`, `Time`, `Date`, `DateTime` and `ActiveSupport::TimeWithZone`. + + *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. @@ -7,9 +26,8 @@ *Yves Senn* - -* Hash.from_xml raises when it encounters type="symbol" or type="yaml". - Use Hash.from_trusted_xml to parse this XML. +* `Hash.from_xml` raises when it encounters `type="symbol"` or `type="yaml"`. + Use `Hash.from_trusted_xml` to parse this XML. CVE-2013-0156 diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 5a5548d567..edbe697962 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -275,34 +275,14 @@ module ActiveSupport if block_given? options = merged_options(options) key = namespaced_key(name, options) - unless options[:force] - entry = instrument(:read, name, options) do |payload| - payload[:super_operation] = :fetch if payload - read_entry(key, options) - end - end - if entry && entry.expired? - race_ttl = options[:race_condition_ttl].to_i - if race_ttl && (Time.now - entry.expires_at <= race_ttl) - # When an entry has :race_condition_ttl defined, put the stale entry back into the cache - # for a brief period while the entry is begin recalculated. - entry.expires_at = Time.now + race_ttl - write_entry(key, entry, :expires_in => race_ttl * 2) - else - delete_entry(key, options) - end - entry = nil - end + + cached_entry = find_cached_entry(key, name, options) unless options[:force] + entry = handle_expired_entry(cached_entry, key, options) if entry - instrument(:fetch_hit, name, options) { |payload| } - entry.value + get_entry_value(entry, name, options) else - result = instrument(:generate, name, options) do |payload| - yield(name) - end - write(name, result, options) - result + save_block_result_to_cache(name, options) { |_name| yield _name } end else read(name, options) @@ -531,6 +511,42 @@ module ActiveSupport return unless logger && logger.debug? && !silence? logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}") end + + def find_cached_entry(key, name, options) + instrument(:read, name, options) do |payload| + payload[:super_operation] = :fetch if payload + read_entry(key, options) + end + end + + def handle_expired_entry(entry, key, options) + if entry && entry.expired? + race_ttl = options[:race_condition_ttl].to_i + if race_ttl && (Time.now - entry.expires_at <= race_ttl) + # When an entry has :race_condition_ttl defined, put the stale entry back into the cache + # for a brief period while the entry is begin recalculated. + entry.expires_at = Time.now + race_ttl + write_entry(key, entry, :expires_in => race_ttl * 2) + else + delete_entry(key, options) + end + entry = nil + end + entry + end + + def get_entry_value(entry, name, options) + instrument(:fetch_hit, name, options) { |payload| } + entry.value + end + + def save_block_result_to_cache(name, options) + result = instrument(:generate, name, options) do |payload| + yield(name) + end + write(name, result, options) + result + end end # This class is used to represent cache entries. Cache entries have a value and an optional diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 712db2c75a..512296554f 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -7,6 +7,7 @@ end require 'digest/md5' require 'active_support/core_ext/marshal' +require 'active_support/core_ext/array/extract_options' module ActiveSupport module Cache @@ -158,7 +159,7 @@ module ActiveSupport # characters properly. def escape_key(key) key = key.to_s.dup - key = key.force_encoding("BINARY") + key = key.force_encoding(Encoding::ASCII_8BIT) key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key 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 4e4852a5e6..1d3682eaf2 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -43,7 +43,7 @@ class DateTime # Returns a new DateTime where one or more of the elements have been changed # according to the +options+ parameter. The time options (<tt>:hour</tt>, - # <tt>:minute</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is + # <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is # passed, then minute and sec is set to 0. If the hour and minute is passed, # then sec is set to 0. The +options+ parameter takes a hash with any of these # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 1079ddde98..534bbe3c42 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -1,35 +1,43 @@ class Object - # Invokes the public method identified by the symbol +method+, passing it any arguments - # and/or the block specified, just like the regular Ruby <tt>Object#public_send</tt> does. + # Invokes the public method whose name goes as first argument just like + # +public_send+ does, except that if the receiver does not respond to it the + # call returns +nil+ rather than raising an exception. # - # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised - # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass. + # This method is defined to be able to write # - # This is also true if the receiving object does not implemented the tried method. It will - # return +nil+ in that case as well. - # - # If try is called without a method to call, it will yield any given block with the object. + # @person.try(:name) # - # Please also note that +try+ is defined on +Object+, therefore it won't work with - # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will - # delegate +try+ to target instead of calling it on delegator itself. + # instead of # - # Without +try+ - # @person && @person.name - # or # @person ? @person.name : nil # - # With +try+ - # @person.try(:name) + # +try+ returns +nil+ when called on +nil+ regardless of whether it responds + # to the method: + # + # nil.try(:to_i) # => nil, rather than 0 + # + # Arguments and blocks are forwarded to the method if invoked: + # + # @posts.try(:each_slice, 2) do |a, b| + # ... + # end + # + # The number of arguments in the signature must match. If the object responds + # to the method the call is attempted and +ArgumentError+ is still raised + # otherwise. # - # +try+ also accepts arguments and/or a block, for the method it is trying - # Person.try(:find, 1) - # @people.try(:collect) {|p| p.name} + # If +try+ is called without arguments it yields the receiver to a given + # block unless it is +nil+: # - # Without a method argument try will yield to the block unless the receiver is nil. - # @person.try { |p| "#{p.first_name} #{p.last_name}" } + # @person.try do |p| + # ... + # end # - # +try+ behaves like +Object#public_send+, unless called on +NilClass+. + # Please also note that +try+ is defined on +Object+, therefore it won't work + # with instances of classes that do not have +Object+ among their ancestors, + # like direct subclasses of +BasicObject+. For example, using +try+ with + # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on + # delegator itself. def try(*a, &b) if a.empty? && block_given? yield self diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index c795df124b..428fa1f826 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -3,26 +3,36 @@ require 'active_support/core_ext/time/calculations' class String # Converts a string to a Time value. - # The +form+ can be either :utc or :local (default :utc). + # The +form+ can be either :utc or :local (default :local). # - # The time is parsed using Date._parse method. - # If +form+ is :local, then time is formatted using Time.zone + # The time is parsed using Time.parse method. + # If +form+ is :local, then the time is in the system timezone. + # If the date part is missing then the current date is used and if + # the time part is missing then it is assumed to be 00:00:00. # - # "3-2-2012".to_time # => 2012-02-03 00:00:00 UTC - # "12:20".to_time # => ArgumentError: invalid date - # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 UTC - # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 UTC - # "2012-12-13T06:12".to_time(:local) # => 2012-12-13 06:12:00 +0100 - def to_time(form = :utc) - unless blank? - date_values = ::Date._parse(self, false). - values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset). - map! { |arg| arg || 0 } - date_values[6] *= 1000000 - offset = date_values.pop + # "13-12-2012".to_time # => 2012-12-13 00:00:00 +0100 + # "06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC + def to_time(form = :local) + parts = Date._parse(self, false) + return if parts.empty? - ::Time.send(form, *date_values) - offset - end + now = Time.now + offset = parts[:offset] + utc_offset = form == :utc ? 0 : now.utc_offset + adjustment = offset ? offset - utc_offset : 0 + + Time.send( + form, + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0) + ) - adjustment end # Converts a string to a Date value. @@ -42,13 +52,6 @@ class String # "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000 # "12/13/2012".to_datetime #=> ArgumentError: invalid date def to_datetime - unless blank? - date_values = ::Date._parse(self, false). - values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction). - map! { |arg| arg || 0 } - date_values[5] += date_values.pop - - ::DateTime.civil(*date_values) - end + ::DateTime.parse(self, false) unless blank? end end diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index e05447439a..a1b3f79748 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -3,6 +3,8 @@ class String # the string, and then changing remaining consecutive whitespace # groups into one space each. # + # Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). + # # %{ Multi-line # string }.squish # => "Multi-line string" # " foo bar \n \t boo".squish # => "foo bar boo" @@ -12,8 +14,9 @@ class String # Performs a destructive squish. See String#squish. def squish! - strip! - gsub!(/\s+/, ' ') + gsub!(/\A[[:space:]]+/, '') + gsub!(/[[:space:]]+\z/, '') + gsub!(/[[:space:]]+/, ' ') self end diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index 6ef33ab683..b837c879bb 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -25,9 +25,9 @@ module ActiveSupport end # Compresses a string using gzip. - def self.compress(source) + def self.compress(source, level=Zlib::DEFAULT_COMPRESSION, strategy=Zlib::DEFAULT_STRATEGY) output = Stream.new - gz = Zlib::GzipWriter.new(output) + gz = Zlib::GzipWriter.new(output, level, strategy) gz.write(source) gz.close output.string diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb index 188653bd9b..22521a8e93 100644 --- a/activesupport/lib/active_support/i18n.rb +++ b/activesupport/lib/active_support/i18n.rb @@ -1,4 +1,7 @@ begin + require 'active_support/core_ext/hash/deep_merge' + require 'active_support/core_ext/hash/except' + require 'active_support/core_ext/hash/slice' require 'i18n' require 'active_support/lazy_load_hooks' rescue LoadError => e diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 9cf4b2b2ba..c96debb93f 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -128,17 +128,29 @@ module ActiveSupport def irregular(singular, plural) @uncountables.delete(singular) @uncountables.delete(plural) - if singular[0,1].upcase == plural[0,1].upcase - plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) - plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1]) - singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) + + s0 = singular[0] + srest = singular[1..-1] + + p0 = plural[0] + prest = plural[1..-1] + + if s0.upcase == p0.upcase + plural(/(#{s0})#{srest}$/i, '\1' + prest) + plural(/(#{p0})#{prest}$/i, '\1' + prest) + + singular(/(#{s0})#{srest}$/i, '\1' + srest) + singular(/(#{p0})#{prest}$/i, '\1' + srest) else - plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) - plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) - plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) - plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) - singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1]) - singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1]) + plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest) + plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest) + plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest) + plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest) + + singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest) + singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest) + singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest) + singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest) end end diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml index f4900dc935..a4563ace8f 100644 --- a/activesupport/lib/active_support/locale/en.yml +++ b/activesupport/lib/active_support/locale/en.yml @@ -16,9 +16,9 @@ en: abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] # Used in date_select and datetime_select. order: - - :year - - :month - - :day + - year + - month + - day time: formats: diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index b7dc0689b0..ce40a7d689 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -1,5 +1,6 @@ require 'openssl' require 'base64' +require 'active_support/core_ext/array/extract_options' module ActiveSupport # MessageEncryptor is a simple way to encrypt values which get stored diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index f49ca47f14..cbc1608349 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -11,7 +11,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = '6.1.0' + UNICODE_VERSION = '6.2.0' # The default normalization used for operations that require # normalization. It can be set to any of the normalizations diff --git a/activesupport/lib/active_support/proxy_object.rb b/activesupport/lib/active_support/proxy_object.rb index a2bdf1d790..20a0fd8e62 100644 --- a/activesupport/lib/active_support/proxy_object.rb +++ b/activesupport/lib/active_support/proxy_object.rb @@ -5,7 +5,7 @@ module ActiveSupport undef_method :== undef_method :equal? - # Let ActiveSupport::BasicObject at least raise exceptions. + # Let ActiveSupport::ProxyObject at least raise exceptions. def raise(*args) ::Object.send(:raise, *args) end diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 72ac597d99..133aa6a054 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -13,6 +13,20 @@ module ActiveSupport end end + # Sets the default value for Time.zone + # If assigned value cannot be matched to a TimeZone, an exception will be raised. + initializer "active_support.initialize_time_zone" do |app| + require 'active_support/core_ext/time/zones' + zone_default = Time.find_zone!(app.config.time_zone) + + unless zone_default + raise 'Value assigned to config.time_zone not recognized. ' \ + 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' + end + + Time.zone_default = zone_default + end + # Sets the default week start # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. initializer "active_support.initialize_beginning_of_week" do |app| @@ -28,21 +42,5 @@ module ActiveSupport ActiveSupport.send(k, v) if ActiveSupport.respond_to? k end end - - # Sets the default value for Time.zone after initialization since the default configuration - # lives in application initializers. - # If assigned value cannot be matched to a TimeZone, an exception will be raised. - config.after_initialize do |app| - require 'active_support/core_ext/time/zones' - zone_default = Time.find_zone!(app.config.time_zone) - - unless zone_default - raise 'Value assigned to config.time_zone not recognized. ' \ - 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' - end - - Time.zone_default = zone_default - end - end end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index aa87598926..dca91e8b75 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,4 +1,9 @@ require 'rbconfig' +begin + require 'minitest/parallel_each' +rescue LoadError +end + module ActiveSupport module Testing class RemoteError < StandardError @@ -43,32 +48,36 @@ module ActiveSupport module Isolation require 'thread' - class ParallelEach - include Enumerable + # Recent versions of MiniTest (such as the one shipped with Ruby 2.0) already define + # a ParallelEach class. + unless defined? ParallelEach + class ParallelEach + include Enumerable - # default to 2 cores - CORES = (ENV['TEST_CORES'] || 2).to_i + # default to 2 cores + CORES = (ENV['TEST_CORES'] || 2).to_i - def initialize list - @list = list - @queue = SizedQueue.new CORES - end + def initialize list + @list = list + @queue = SizedQueue.new CORES + end - def grep pattern - self.class.new super - end + def grep pattern + self.class.new super + end - def each - threads = CORES.times.map { - Thread.new { - while job = @queue.pop - yield job - end + def each + threads = CORES.times.map { + Thread.new { + while job = @queue.pop + yield job + end + } } - } - @list.each { |i| @queue << i } - CORES.times { @queue << nil } - threads.each(&:join) + @list.each { |i| @queue << i } + CORES.times { @queue << nil } + threads.each(&:join) + end end end @@ -84,10 +93,14 @@ module ActiveSupport !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) end + @@class_setup_mutex = Mutex.new + def _run_class_setup # class setup method should only happen in parent - unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST'] - self.class.setup if self.class.respond_to?(:setup) - @@ran_class_setup = true + @@class_setup_mutex.synchronize do + unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST'] + self.class.setup if self.class.respond_to?(:setup) + @@ran_class_setup = true + end end end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index d3741845d2..0e6d12a186 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -154,7 +154,7 @@ module ActiveSupport # # => "2005/02/01 15:15:10 +0000" def as_json(options = nil) if ActiveSupport::JSON::Encoding.use_standard_json_time_format - xmlschema + xmlschema(3) else %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end @@ -317,9 +317,9 @@ module ActiveSupport end alias_method :tv_sec, :to_i - # A TimeWithZone acts like a Time, so just return +self+. + # Return an instance of Time in the system timezone. def to_time - utc + utc.to_time end def to_datetime diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat Binary files differindex df17a8cccf..2571faa019 100644 --- a/activesupport/lib/active_support/values/unicode_tables.dat +++ b/activesupport/lib/active_support/values/unicode_tables.dat diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 90e50f235b..dd17cb64f4 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -21,5 +21,7 @@ require 'empty_bool' ENV['NO_RELOAD'] = '1' require 'active_support' +Thread.abort_on_exception = true + # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index ec47d0632c..f3fa96ec6f 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -33,8 +33,12 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_to_time - assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time - assert_equal Time.local(2039, 2, 21), Date.new(2039, 2, 21).to_time + with_env_tz 'US/Eastern' do + assert_equal Time, Date.new(2005, 2, 21).to_time.class + assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time + assert_equal Time.local(2005, 2, 21).utc_offset, Date.new(2005, 2, 21).to_time.utc_offset + end + silence_warnings do 0.upto(138) do |year| [:utc, :local].each do |format| diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 54bbdbb18f..24e62cc2b9 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -41,11 +41,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_to_time - assert_equal Time.utc(2005, 2, 21, 10, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time - assert_equal Time.utc(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0).to_time - # DateTimes with offsets other than 0 are returned unaltered - assert_equal DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)), DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).to_time - # Fractional seconds are preserved + with_env_tz 'US/Eastern' do + assert_equal Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.class + assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time + assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset + end + end + + def test_to_time_preserves_fractional_seconds assert_equal Time.utc(2005, 2, 21, 10, 11, 12, 256), DateTime.new(2005, 2, 21, 10, 11, 12 + Rational(256, 1000000), 0).to_time end @@ -242,6 +245,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end end + def test_acts_like_date + assert DateTime.new.acts_like_date? + end + def test_acts_like_time assert DateTime.new.acts_like_time? end diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index 1583c1fa32..b8951de402 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -38,6 +38,18 @@ class KernelTest < ActiveSupport::TestCase # Skip if we can't STDERR.tell end + def test_quietly + old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell + quietly do + puts 'see me, feel me' + STDERR.puts 'touch me, heal me' + end + assert_equal old_stdout_position, STDOUT.tell + assert_equal old_stderr_position, STDERR.tell + rescue Errno::ESPIPE + # Skip if we can't STDERR.tell + end + def test_silence_stderr_with_return_value assert_equal 1, silence_stderr { 1 } end diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index ec7dd6d4fb..8d748791e3 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -120,6 +120,10 @@ class ObjectTryTest < ActiveSupport::TestCase assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') } end + def test_try_only_block_bang + assert_equal @string.reverse, @string.try! { |s| s.reverse } + end + def test_valid_method assert_equal 5, @string.try(:size) end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index e0ddeab548..bff155f045 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -87,6 +87,12 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal('capital', 'Capital'.camelize(:lower)) end + def test_dasherize + UnderscoresToDashes.each do |underscored, dasherized| + assert_equal(dasherized, underscored.dasherize) + end + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, camel.underscore) @@ -223,10 +229,11 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_string_squish - original = %{ A string with tabs(\t\t), newlines(\n\n), and - many spaces( ). } + original = %{\u180E\u180E A string surrounded by unicode mongolian vowel separators, + with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u180E\u180E} - expected = "A string with tabs( ), newlines( ), and many spaces( )." + expected = "A string surrounded by unicode mongolian vowel separators, " + + "with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )." # Make sure squish returns what we expect: assert_equal original.squish, expected @@ -263,8 +270,8 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_truncate_multibyte - assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), - "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10) + assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8), + "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8).truncate(10) end def test_truncate_should_not_be_html_safe @@ -286,14 +293,37 @@ end class StringConversionsTest < ActiveSupport::TestCase def test_string_to_time - assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time - assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:local) - assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time - assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local) - assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time - assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local) - assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time - assert_nil "".to_time + with_env_tz "US/Eastern" do + assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:utc) + assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time + assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:utc) + assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time + assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:utc) + assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time + assert_equal Time.local(2011, 2, 27, 18, 50), "2011-02-27 22:50 -0100".to_time + assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc) + assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50 -0500".to_time + assert_nil "".to_time + end + end + + def test_string_to_time_utc_offset + with_env_tz "US/Eastern" do + assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset + assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset) + assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset + assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset) + end + end + + def test_partial_string_to_time + with_env_tz "US/Eastern" do + now = Time.now + assert_equal Time.local(now.year, now.month, now.day, 23, 50), "23:50".to_time + assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "23:50".to_time(:utc) + assert_equal Time.local(now.year, now.month, now.day, 18, 50), "22:50 -0100".to_time + assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "22:50 -0100".to_time(:utc) + end end def test_string_to_datetime @@ -304,11 +334,25 @@ class StringConversionsTest < ActiveSupport::TestCase assert_nil "".to_datetime end + def test_partial_string_to_datetime + now = DateTime.now + assert_equal DateTime.civil(now.year, now.month, now.day, 23, 50), "23:50".to_datetime + assert_equal DateTime.civil(now.year, now.month, now.day, 23, 50, 0, "-04:00"), "23:50 -0400".to_datetime + end + def test_string_to_date assert_equal Date.new(2005, 2, 27), "2005-02-27".to_date assert_nil "".to_date assert_equal Date.new(Date.today.year, 2, 3), "Feb 3rd".to_date end + + protected + def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield + ensure + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + end end class StringBehaviourTest < ActiveSupport::TestCase @@ -318,22 +362,24 @@ class StringBehaviourTest < ActiveSupport::TestCase end class CoreExtStringMultibyteTest < ActiveSupport::TestCase - UNICODE_STRING = 'こにちわ' - ASCII_STRING = 'ohayo' - BYTE_STRING = "\270\236\010\210\245" + UTF8_STRING = 'こにちわ' + ASCII_STRING = 'ohayo'.encode('US-ASCII') + EUC_JP_STRING = 'さよなら'.encode('EUC-JP') + INVALID_UTF8_STRING = "\270\236\010\210\245" def test_core_ext_adds_mb_chars - assert_respond_to UNICODE_STRING, :mb_chars + assert_respond_to UTF8_STRING, :mb_chars end def test_string_should_recognize_utf8_strings - assert UNICODE_STRING.is_utf8? + assert UTF8_STRING.is_utf8? assert ASCII_STRING.is_utf8? - assert !BYTE_STRING.is_utf8? + assert !EUC_JP_STRING.is_utf8? + assert !INVALID_UTF8_STRING.is_utf8? end def test_mb_chars_returns_instance_of_proxy_class - assert_kind_of ActiveSupport::Multibyte.proxy_class, UNICODE_STRING.mb_chars + assert_kind_of ActiveSupport::Multibyte.proxy_class, UTF8_STRING.mb_chars end end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index a2fefee3b8..43c92003dc 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -526,7 +526,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_to_time - assert_equal Time.local(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30).to_time + with_env_tz 'US/Eastern' do + assert_equal Time, Time.local(2005, 2, 21, 17, 44, 30).to_time.class + assert_equal Time.local(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30).to_time + assert_equal Time.local(2005, 2, 21, 17, 44, 30).utc_offset, Time.local(2005, 2, 21, 17, 44, 30).to_time.utc_offset + end end # NOTE: this test seems to fail (changeset 1958) only on certain platforms, diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 6c773770f0..c2b3676aac 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -75,7 +75,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_to_json_with_use_standard_json_time_format_config_set_to_true old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, true - assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(@twz) + assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(@twz) ensure ActiveSupport.use_standard_json_time_format = old end @@ -327,7 +327,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_to_time - assert_equal @twz, @twz.to_time + with_env_tz 'US/Eastern' do + assert_equal Time, @twz.to_time.class + assert_equal Time.local(1999, 12, 31, 19), @twz.to_time + assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset + end end def test_to_date diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index c1a468ec86..9616e42f44 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -75,6 +75,11 @@ class DeprecationTest < ActiveSupport::TestCase end end + def test_deprecate_object + deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, ':bomb:') + assert_deprecated(/:bomb:/) { deprecated_object.to_s } + end + def test_nil_behavior_is_ignored ActiveSupport::Deprecation.behavior = nil assert_deprecated(/foo=nil/) { @dtc.partially } @@ -139,6 +144,7 @@ class DeprecationTest < ActiveSupport::TestCase def test_deprecated_constant_proxy assert_not_deprecated { Deprecatee::B::C } assert_deprecated('Deprecatee::A') { assert_equal Deprecatee::B::C, Deprecatee::A } + assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class } end def test_assert_deprecation_without_match diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb index 75a0505899..0e3cf3b429 100644 --- a/activesupport/test/gzip_test.rb +++ b/activesupport/test/gzip_test.rb @@ -4,6 +4,12 @@ require 'active_support/core_ext/object/blank' class GzipTest < ActiveSupport::TestCase def test_compress_should_decompress_to_the_same_value assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World")) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::NO_COMPRESSION)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::BEST_SPEED)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::BEST_COMPRESSION)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, Zlib::FILTERED)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, Zlib::HUFFMAN_ONLY)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, nil)) end def test_compress_should_return_a_binary_string @@ -12,4 +18,16 @@ class GzipTest < ActiveSupport::TestCase assert_equal Encoding.find('binary'), compressed.encoding assert !compressed.blank?, "a compressed blank string should not be blank" end + + def test_compress_should_return_gzipped_string_by_compression_level + source_string = "Hello World"*100 + + gzipped_by_speed = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_SPEED) + assert_equal 1, Zlib::GzipReader.new(StringIO.new(gzipped_by_speed)).level + + gzipped_by_best_compression = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_COMPRESSION) + assert_equal 9, Zlib::GzipReader.new(StringIO.new(gzipped_by_best_compression)).level + + assert_equal true, (gzipped_by_best_compression.bytesize < gzipped_by_speed.bytesize) + end end diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index ddbba444cf..5ef59b6e6b 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -62,7 +62,7 @@ class I18nTest < ActiveSupport::TestCase end def test_date_order - assert_equal [:year, :month, :day], I18n.translate(:'date.order') + assert_equal %w(year month day), I18n.translate(:'date.order') end def test_time_am diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index ca4efd2e59..7704300938 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -310,5 +310,6 @@ module InflectorTestCases 'move' => 'moves', 'cow' => 'kine', 'zombie' => 'zombies', + 'genus' => 'genera' } end diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 14ba4e0076..6aea9d56f1 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -2,6 +2,7 @@ require 'abstract_unit' require 'active_support/json' require 'active_support/core_ext/object/to_json' require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/array/extract_options' class OrderedHashTest < ActiveSupport::TestCase def setup diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb index 3f8d09c18e..e099e47e0e 100644 --- a/activesupport/test/rescuable_test.rb +++ b/activesupport/test/rescuable_test.rb @@ -21,7 +21,7 @@ class Stargate rescue_from WraithAttack, :with => :sos - rescue_from NuclearExplosion do + rescue_from 'NuclearExplosion' do @result = 'alldead' end @@ -102,5 +102,4 @@ class RescuableTest < ActiveSupport::TestCase result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first} assert_equal expected, result end - end diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png Binary files differnew file mode 100644 index 0000000000..500dfc2c02 --- /dev/null +++ b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png Binary files differindex 407ea2ea06..43ccd25252 100644 --- a/guides/assets/images/getting_started/routing_error_no_controller.png +++ b/guides/assets/images/getting_started/routing_error_no_controller.png diff --git a/guides/assets/images/getting_started/routing_error_no_route_matches.png b/guides/assets/images/getting_started/routing_error_no_route_matches.png Binary files differindex d461807c5d..1b8c0ea57e 100644 --- a/guides/assets/images/getting_started/routing_error_no_route_matches.png +++ b/guides/assets/images/getting_started/routing_error_no_route_matches.png diff --git a/guides/assets/images/getting_started/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png Binary files differindex 6860aaeca7..75980432b2 100644 --- a/guides/assets/images/getting_started/template_is_missing_posts_new.png +++ b/guides/assets/images/getting_started/template_is_missing_posts_new.png diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png Binary files differindex 1eca14b988..c6750e1ae1 100644 --- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.png diff --git a/guides/assets/images/getting_started/unknown_action_new_for_posts.png b/guides/assets/images/getting_started/unknown_action_new_for_posts.png Binary files differindex fd72586573..f4b3eff9dc 100644 --- a/guides/assets/images/getting_started/unknown_action_new_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_new_for_posts.png diff --git a/guides/code/getting_started/.gitignore b/guides/code/getting_started/.gitignore new file mode 100644 index 0000000000..25a742dff0 --- /dev/null +++ b/guides/code/getting_started/.gitignore @@ -0,0 +1,16 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/*.log +/tmp diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile index 670a8523b0..b355c7d91a 100644 --- a/guides/code/getting_started/Gemfile +++ b/guides/code/getting_started/Gemfile @@ -1,38 +1,38 @@ source 'https://rubygems.org' -gem 'rails', '3.2.3' - -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' +gem 'rails', '4.0.0' gem 'sqlite3' - # Gems used only for assets and not required # in production environments by default. group :assets do - gem 'sass-rails', '~> 3.2.3' - gem 'coffee-rails', '~> 3.2.1' + gem 'sprockets-rails' + gem 'sass-rails' + gem 'coffee-rails' # See https://github.com/sstephenson/execjs#readme for more supported runtimes - # gem 'therubyracer', :platform => :ruby + # gem 'therubyracer', platforms: :ruby gem 'uglifier', '>= 1.0.3' end gem 'jquery-rails' +# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks +gem 'turbolinks' + +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 1.0.1' + # To use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' -# To use Jbuilder templates for JSON -# gem 'jbuilder' - # Use unicorn as the app server # gem 'unicorn' # Deploy with Capistrano -# gem 'capistrano' +# gem 'capistrano', group: :development # To use debugger # gem 'debugger' diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock new file mode 100644 index 0000000000..823fac5ff7 --- /dev/null +++ b/guides/code/getting_started/Gemfile.lock @@ -0,0 +1,150 @@ +GIT + remote: git://github.com/rails/activerecord-deprecated_finders.git + revision: 2e7b35d7948cefb2bba96438873d7f7bb1961a03 + specs: + activerecord-deprecated_finders (0.0.2) + +GIT + remote: git://github.com/rails/arel.git + revision: 38d0a222e275d917a2c1d093b24457bafb600a00 + specs: + arel (3.0.2.20120819075748) + +GIT + remote: git://github.com/rails/coffee-rails.git + revision: 052634e6d02d4800d7b021201cc8d5829775b3cd + specs: + coffee-rails (4.0.0.beta) + coffee-script (>= 2.2.0) + railties (>= 4.0.0.beta, < 5.0) + +GIT + remote: git://github.com/rails/sass-rails.git + revision: ae8138a89cac397c0df903dd533e2862902ce8f5 + specs: + sass-rails (4.0.0.beta) + railties (>= 4.0.0.beta, < 5.0) + sass (>= 3.1.10) + sprockets-rails (~> 2.0.0.rc0) + tilt (~> 1.3) + +GIT + remote: git://github.com/rails/sprockets-rails.git + revision: 09917104fdb42245fe369612a7b0e3d77e1ba763 + specs: + sprockets-rails (2.0.0.rc1) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (~> 2.8) + +PATH + remote: /Users/steve/src/rails + specs: + actionmailer (4.0.0.beta) + actionpack (= 4.0.0.beta) + mail (~> 2.5.3) + actionpack (4.0.0.beta) + activesupport (= 4.0.0.beta) + builder (~> 3.1.0) + erubis (~> 2.7.0) + rack (~> 1.4.3) + rack-test (~> 0.6.1) + activemodel (4.0.0.beta) + activesupport (= 4.0.0.beta) + builder (~> 3.1.0) + activerecord (4.0.0.beta) + activemodel (= 4.0.0.beta) + activerecord-deprecated_finders (= 0.0.2) + activesupport (= 4.0.0.beta) + arel (~> 3.0.2) + activesupport (4.0.0.beta) + i18n (~> 0.6) + minitest (~> 4.1) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.33) + rails (4.0.0.beta) + actionmailer (= 4.0.0.beta) + actionpack (= 4.0.0.beta) + activerecord (= 4.0.0.beta) + activesupport (= 4.0.0.beta) + bundler (>= 1.2.2, < 2.0) + railties (= 4.0.0.beta) + sprockets-rails (~> 2.0.0.rc1) + railties (4.0.0.beta) + actionpack (= 4.0.0.beta) + activesupport (= 4.0.0.beta) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (>= 0.15.4, < 2.0) + +GEM + remote: https://rubygems.org/ + specs: + atomic (1.0.1) + builder (3.1.4) + coffee-script (2.2.0) + coffee-script-source + execjs + coffee-script-source (1.4.0) + erubis (2.7.0) + execjs (1.4.0) + multi_json (~> 1.0) + hike (1.2.1) + i18n (0.6.1) + jbuilder (1.0.2) + activesupport (>= 3.0.0) + jquery-rails (2.2.0) + railties (>= 3.0, < 5.0) + thor (>= 0.14, < 2.0) + json (1.7.6) + mail (2.5.3) + i18n (>= 0.4.0) + mime-types (~> 1.16) + treetop (~> 1.4.8) + mime-types (1.19) + minitest (4.4.0) + multi_json (1.5.0) + polyglot (0.3.3) + rack (1.4.4) + rack-test (0.6.2) + rack (>= 1.0) + rake (10.0.3) + rdoc (3.12) + json (~> 1.4) + sass (3.2.5) + sprockets (2.8.2) + hike (~> 1.2) + multi_json (~> 1.0) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sqlite3 (1.3.7) + thor (0.16.0) + thread_safe (0.1.0) + atomic + tilt (1.3.3) + treetop (1.4.12) + polyglot + polyglot (>= 0.3.1) + turbolinks (1.0.0) + coffee-rails + tzinfo (0.3.35) + uglifier (1.3.0) + execjs (>= 0.3.0) + multi_json (~> 1.0, >= 1.0.2) + +PLATFORMS + ruby + +DEPENDENCIES + activerecord-deprecated_finders! + arel! + coffee-rails! + jbuilder (~> 1.0.1) + jquery-rails + rails! + sass-rails! + sprockets-rails! + sqlite3 + turbolinks + uglifier (>= 1.0.3) diff --git a/guides/code/getting_started/README.rdoc b/guides/code/getting_started/README.rdoc index 232856ca5f..dd4e97e22e 100644 --- a/guides/code/getting_started/README.rdoc +++ b/guides/code/getting_started/README.rdoc @@ -23,6 +23,6 @@ Things you may want to cover: * ... -If you plan to generate application documentation with `rake doc:app` this file -is expected to be `README.rdoc`, otherwise please feel free to rename it and use -a different markup language.
\ No newline at end of file + +Please feel free to use a different markup language if you do not plan to run +<tt>rake doc:app</tt>. diff --git a/guides/code/getting_started/Rakefile b/guides/code/getting_started/Rakefile index e1d1ec8615..05de8bb536 100644 --- a/guides/code/getting_started/Rakefile +++ b/guides/code/getting_started/Rakefile @@ -1,4 +1,3 @@ -#!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. diff --git a/guides/code/getting_started/app/assets/javascripts/application.js b/guides/code/getting_started/app/assets/javascripts/application.js index 93cdae76ca..9e83eb5e7e 100644 --- a/guides/code/getting_started/app/assets/javascripts/application.js +++ b/guides/code/getting_started/app/assets/javascripts/application.js @@ -12,4 +12,5 @@ // //= require jquery //= require jquery_ujs +//= require turbolinks //= require_tree . diff --git a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee b/guides/code/getting_started/app/assets/javascripts/comments.js.coffee new file mode 100644 index 0000000000..24f83d18bb --- /dev/null +++ b/guides/code/getting_started/app/assets/javascripts/comments.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee b/guides/code/getting_started/app/assets/javascripts/posts.js.coffee new file mode 100644 index 0000000000..24f83d18bb --- /dev/null +++ b/guides/code/getting_started/app/assets/javascripts/posts.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee b/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee new file mode 100644 index 0000000000..24f83d18bb --- /dev/null +++ b/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/guides/code/getting_started/app/assets/stylesheets/application.css b/guides/code/getting_started/app/assets/stylesheets/application.css index 3b5cc6648e..3192ec897b 100644 --- a/guides/code/getting_started/app/assets/stylesheets/application.css +++ b/guides/code/getting_started/app/assets/stylesheets/application.css @@ -10,4 +10,4 @@ * *= require_self *= require_tree . -*/ + */ diff --git a/guides/code/getting_started/app/assets/stylesheets/comments.css.scss b/guides/code/getting_started/app/assets/stylesheets/comments.css.scss new file mode 100644 index 0000000000..e730912783 --- /dev/null +++ b/guides/code/getting_started/app/assets/stylesheets/comments.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Comments controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss b/guides/code/getting_started/app/assets/stylesheets/posts.css.scss new file mode 100644 index 0000000000..1a7e15390c --- /dev/null +++ b/guides/code/getting_started/app/assets/stylesheets/posts.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the posts controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss b/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss new file mode 100644 index 0000000000..77ce11a740 --- /dev/null +++ b/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the welcome controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/controllers/application_controller.rb b/guides/code/getting_started/app/controllers/application_controller.rb index e8065d9505..d83690e1b9 100644 --- a/guides/code/getting_started/app/controllers/application_controller.rb +++ b/guides/code/getting_started/app/controllers/application_controller.rb @@ -1,3 +1,5 @@ class ApplicationController < ActionController::Base - protect_from_forgery + # Prevent CSRF attacks by raising an exception. + # For APIs, you may want to use :null_session instead. + protect_from_forgery with: :exception end diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb index cf3d1be42e..0082e9c8ec 100644 --- a/guides/code/getting_started/app/controllers/comments_controller.rb +++ b/guides/code/getting_started/app/controllers/comments_controller.rb @@ -1,9 +1,10 @@ class CommentsController < ApplicationController - http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy + http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy + def create @post = Post.find(params[:post_id]) - @comment = @post.comments.create(params[:comment]) + @comment = @post.comments.create(params[:comment].permit(:commenter, :body)) redirect_to post_path(@post) end @@ -13,5 +14,4 @@ class CommentsController < ApplicationController @comment.destroy redirect_to post_path(@post) end - end diff --git a/guides/code/getting_started/app/mailers/.gitkeep b/guides/code/getting_started/app/controllers/concerns/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/app/mailers/.gitkeep +++ b/guides/code/getting_started/app/controllers/concerns/.keep diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb index b74c66ef13..0398395200 100644 --- a/guides/code/getting_started/app/controllers/posts_controller.rb +++ b/guides/code/getting_started/app/controllers/posts_controller.rb @@ -1,7 +1,7 @@ class PostsController < ApplicationController - http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show] - + http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] + def index @posts = Post.all end @@ -10,31 +10,31 @@ class PostsController < ApplicationController @post = Post.find(params[:id]) end - def new - @post = Post.new + def edit + @post = Post.find(params[:id]) end - def create - @post = Post.new(params[:post]) + def update + @post = Post.find(params[:id]) - if @post.save - redirect_to :action => :show, :id => @post.id + if @post.update(params[:post].permit(:title, :text)) + redirect_to action: :show, id: @post.id else - render 'new' + render 'edit' end end - def edit - @post = Post.find(params[:id]) + def new + @post = Post.new end - def update - @post = Post.find(params[:id]) + def create + @post = Post.new(params[:post].permit(:title, :text)) - if @post.update(params[:post]) - redirect_to :action => :show, :id => @post.id + if @post.save + redirect_to action: :show, id: @post.id else - render 'edit' + render 'new' end end @@ -42,6 +42,6 @@ class PostsController < ApplicationController @post = Post.find(params[:id]) @post.destroy - redirect_to :action => :index + redirect_to action: :index end end diff --git a/guides/code/getting_started/app/models/.gitkeep b/guides/code/getting_started/app/mailers/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/app/models/.gitkeep +++ b/guides/code/getting_started/app/mailers/.keep diff --git a/guides/code/getting_started/lib/assets/.gitkeep b/guides/code/getting_started/app/models/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/lib/assets/.gitkeep +++ b/guides/code/getting_started/app/models/.keep diff --git a/guides/code/getting_started/lib/tasks/.gitkeep b/guides/code/getting_started/app/models/concerns/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/lib/tasks/.gitkeep +++ b/guides/code/getting_started/app/models/concerns/.keep diff --git a/guides/code/getting_started/app/models/post.rb b/guides/code/getting_started/app/models/post.rb index 21387340b0..64e0d721fd 100644 --- a/guides/code/getting_started/app/models/post.rb +++ b/guides/code/getting_started/app/models/post.rb @@ -1,6 +1,7 @@ class Post < ActiveRecord::Base - validates :title, :presence => true, - :length => { :minimum => 5 } - - has_many :comments, :dependent => :destroy + has_many :comments, dependent: :destroy + + validates :title, + presence: true, + length: { minimum: 5 } end diff --git a/guides/code/getting_started/app/views/comments/_comment.html.erb b/guides/code/getting_started/app/views/comments/_comment.html.erb index 3d2bc1590e..593493339e 100644 --- a/guides/code/getting_started/app/views/comments/_comment.html.erb +++ b/guides/code/getting_started/app/views/comments/_comment.html.erb @@ -2,7 +2,7 @@ <strong>Commenter:</strong> <%= comment.commenter %> </p> - + <p> <strong>Comment:</strong> <%= comment.body %> @@ -10,6 +10,6 @@ <p> <%= link_to 'Destroy Comment', [comment.post, comment], - :method => :delete, - :data => { :confirm => 'Are you sure?' } %> + method: :delete, + data: { confirm: 'Are you sure?' } %> </p> diff --git a/guides/code/getting_started/app/views/layouts/application.html.erb b/guides/code/getting_started/app/views/layouts/application.html.erb index 6578a41da2..95368c37a3 100644 --- a/guides/code/getting_started/app/views/layouts/application.html.erb +++ b/guides/code/getting_started/app/views/layouts/application.html.erb @@ -2,8 +2,8 @@ <html> <head> <title>Blog</title> - <%= stylesheet_link_tag "application", :media => "all" %> - <%= javascript_include_tag "application" %> + <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> + <%= javascript_include_tag "application", "data-turbolinks-track" => true %> <%= csrf_meta_tags %> </head> <body> diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/posts/_form.html.erb index f22139938c..c9fb74af9c 100644 --- a/guides/code/getting_started/app/views/posts/_form.html.erb +++ b/guides/code/getting_started/app/views/posts/_form.html.erb @@ -1,25 +1,27 @@ <%= form_for @post do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> - <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> - <ul> - <% @post.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> + <div id="errorExplanation"> + <h2><%= pluralize(@post.errors.count, "error") %> prohibited + this post from being saved:</h2> + <ul> + <% @post.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> <% end %> <p> - <%= f.label :title %><br /> + <%= f.label :title %><br> <%= f.text_field :title %> </p> - + <p> - <%= f.label :text %><br /> + <%= f.label :text %><br> <%= f.text_area :text %> </p> - + <p> <%= f.submit %> </p> <% end %> + diff --git a/guides/code/getting_started/app/views/posts/edit.html.erb b/guides/code/getting_started/app/views/posts/edit.html.erb index 911a48569d..393e7430d0 100644 --- a/guides/code/getting_started/app/views/posts/edit.html.erb +++ b/guides/code/getting_started/app/views/posts/edit.html.erb @@ -1,5 +1,5 @@ -<h1>Editing post</h1> - +<h1>Edit post</h1> + <%= render 'form' %> - -<%= link_to 'Back', :action => :index %> + +<%= link_to 'Back', action: :index %> diff --git a/guides/code/getting_started/app/views/posts/index.html.erb b/guides/code/getting_started/app/views/posts/index.html.erb index 9a0e90eadc..7369f0396f 100644 --- a/guides/code/getting_started/app/views/posts/index.html.erb +++ b/guides/code/getting_started/app/views/posts/index.html.erb @@ -1,7 +1,4 @@ -<h1>Listing posts</h1> - -<%= link_to 'New post', :action => :new %> - +<h1>Listing Posts</h1> <table> <tr> <th>Title</th> @@ -10,14 +7,15 @@ <th></th> <th></th> </tr> - + <% @posts.each do |post| %> <tr> <td><%= post.title %></td> <td><%= post.text %></td> - <td><%= link_to 'Show', :action => :show, :id => post.id %> - <td><%= link_to 'Edit', :action => :edit, :id => post.id %> - <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :data => { :confirm => 'Are you sure?' } %> + <td><%= link_to 'Show', action: :show, id: post.id %></td> + <td><%= link_to 'Edit', action: :edit, id: post.id %></td> + <td><%= link_to 'Destroy', { action: :destroy, id: post.id }, + method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </table> diff --git a/guides/code/getting_started/app/views/posts/new.html.erb b/guides/code/getting_started/app/views/posts/new.html.erb index ce9523a721..efa81038ec 100644 --- a/guides/code/getting_started/app/views/posts/new.html.erb +++ b/guides/code/getting_started/app/views/posts/new.html.erb @@ -1,5 +1,5 @@ <h1>New post</h1> - + <%= render 'form' %> - -<%= link_to 'Back', :action => :index %> + +<%= link_to 'Back', action: :index %> diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb index 65809033ed..e99e9edbb3 100644 --- a/guides/code/getting_started/app/views/posts/show.html.erb +++ b/guides/code/getting_started/app/views/posts/show.html.erb @@ -2,7 +2,7 @@ <strong>Title:</strong> <%= @post.title %> </p> - + <p> <strong>Text:</strong> <%= @post.text %> @@ -10,9 +10,9 @@ <h2>Comments</h2> <%= render @post.comments %> - + <h2>Add a comment:</h2> <%= render "comments/form" %> - + <%= link_to 'Edit Post', edit_post_path(@post) %> | <%= link_to 'Back to Posts', posts_path %> diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb index e04680ea7e..738e12d7dc 100644 --- a/guides/code/getting_started/app/views/welcome/index.html.erb +++ b/guides/code/getting_started/app/views/welcome/index.html.erb @@ -1,2 +1,3 @@ <h1>Hello, Rails!</h1> -<%= link_to "My Blog", :controller => "posts" %> + +<%= link_to "My Blog", controller: "posts" %> diff --git a/guides/code/getting_started/bin/bundle b/guides/code/getting_started/bin/bundle new file mode 100755 index 0000000000..45cf37fba4 --- /dev/null +++ b/guides/code/getting_started/bin/bundle @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +require 'rubygems' +load Gem.bin_path('bundler', 'bundle') diff --git a/guides/code/getting_started/bin/rails b/guides/code/getting_started/bin/rails new file mode 100755 index 0000000000..728cd85aa5 --- /dev/null +++ b/guides/code/getting_started/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/guides/code/getting_started/bin/rake b/guides/code/getting_started/bin/rake new file mode 100755 index 0000000000..17240489f6 --- /dev/null +++ b/guides/code/getting_started/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/guides/code/getting_started/config/application.rb b/guides/code/getting_started/config/application.rb index d53c9fd8bc..526a782b5c 100644 --- a/guides/code/getting_started/config/application.rb +++ b/guides/code/getting_started/config/application.rb @@ -2,12 +2,8 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line - Bundler.require(*Rails.groups(:assets => %w(development test))) - # If you want your assets lazily compiled in production, use this line - # Bundler.require(:default, :assets, Rails.env) -end +# Assets should be precompiled for production (so we don't need the gems loaded then) +Bundler.require(*Rails.groups(assets: %w(development test))) module Blog class Application < Rails::Application @@ -17,36 +13,5 @@ module Blog # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) - - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # config.time_zone = 'Central Time (US & Canada)' - - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - # config.i18n.default_locale = :de - - # Configure the default encoding used in templates for Ruby 1.9. - config.encoding = "utf-8" - - # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters += [:password] - - # Use SQL instead of Active Record's schema dumper when creating the database. - # This is necessary if your schema can't be completely dumped by the schema dumper, - # like if you have constraints or database-specific column types. - # config.active_record.schema_format = :sql - - # Enforce whitelist mode for mass assignment. - # This will create an empty whitelist of attributes available for mass-assignment for all models - # in your app. As such, your models will need to explicitly whitelist or blacklist accessible - # parameters by using an attr_accessible or attr_protected declaration. - # config.active_record.whitelist_attributes = true - - # Enable the asset pipeline. - config.assets.enabled = true - - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0' end end diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb index 8f728b7ce7..2d65111004 100644 --- a/guides/code/getting_started/config/environment.rb +++ b/guides/code/getting_started/config/environment.rb @@ -1,5 +1,5 @@ -# Load the rails application +# Load the rails application. require File.expand_path('../application', __FILE__) -# Initialize the rails application +# Initialize the rails application. Blog::Application.initialize! diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb index cec2b20c0b..ed2667ac58 100644 --- a/guides/code/getting_started/config/environments/development.rb +++ b/guides/code/getting_started/config/environments/development.rb @@ -2,10 +2,13 @@ Blog::Application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on - # every request. This slows down response time but is perfect for development + # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false + # Do not eager load code on boot. + config.eager_load = false + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -19,16 +22,13 @@ Blog::Application.configure do # Only use best-standards-support built into browsers. config.action_dispatch.best_standards_support = :builtin - # Raise exception on mass assignment protection for ActiveRecord models. - config.active_record.mass_assignment_sanitizer = :strict - # 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 - # Do not compress assets. - config.assets.compress = false + # Raise an error on page load if there are pending migrations + config.active_record.migration_error = :page_load - # Expands the lines which load the assets. + # Debug mode disables concatenation and preprocessing of assets. config.assets.debug = true end diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb index ecc35b030b..58dab2a319 100644 --- a/guides/code/getting_started/config/environments/production.rb +++ b/guides/code/getting_started/config/environments/production.rb @@ -4,24 +4,36 @@ Blog::Application.configure do # Code is not reloaded between requests. config.cache_classes = true + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both thread web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. + # config.action_dispatch.rack_cache = true + # Disable Rails's static asset server (Apache or nginx will already do this). config.serve_static_assets = false # Compress JavaScripts and CSS. - config.assets.compress = true + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass - # Don't fallback to assets pipeline if a precompiled asset is missed. + # Whether to fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false # Generate digests for assets URLs. config.assets.digest = true - # Defaults to nil - # config.assets.manifest = YOUR_PATH + # Version of your assets, change this if you want to expire all your assets. + config.assets.version = '1.0' # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache @@ -30,8 +42,8 @@ Blog::Application.configure do # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true - # See everything in the log (default is :info). - # config.log_level = :debug + # Set to :debug to see everything in the log. + config.log_level = :info # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] @@ -45,15 +57,14 @@ Blog::Application.configure do # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = "http://assets.example.com" - # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added). + # 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 ) - # Disable delivery errors, bad email addresses will be ignored. + # 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 - # Enable threaded mode. - # config.threadsafe! - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found). config.i18n.fallbacks = true @@ -64,4 +75,10 @@ Blog::Application.configure do # 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 + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new end diff --git a/guides/code/getting_started/config/environments/test.rb b/guides/code/getting_started/config/environments/test.rb index f2bc932fb3..00adaa5015 100644 --- a/guides/code/getting_started/config/environments/test.rb +++ b/guides/code/getting_started/config/environments/test.rb @@ -2,11 +2,16 @@ Blog::Application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's - # test suite. You never need to work with it otherwise. Remember that + # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped - # and recreated between test runs. Don't rely on the data there! + # and recreated between test runs. Don't rely on the data there! config.cache_classes = true + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + # Configure static asset server for tests with Cache-Control for performance. config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" @@ -26,9 +31,6 @@ Blog::Application.configure do # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - # Raise exception on mass assignment protection for Active Record models. - config.active_record.mass_assignment_sanitizer = :strict - # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr end diff --git a/guides/code/getting_started/config/initializers/filter_parameter_logging.rb b/guides/code/getting_started/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /dev/null +++ b/guides/code/getting_started/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/guides/code/getting_started/config/initializers/inflections.rb b/guides/code/getting_started/config/initializers/inflections.rb index 5d8d9be237..ac033bf9dc 100644 --- a/guides/code/getting_started/config/initializers/inflections.rb +++ b/guides/code/getting_started/config/initializers/inflections.rb @@ -1,15 +1,16 @@ # Be sure to restart your server when you modify this file. -# Add new inflection rules using the following format -# (all these examples are active by default): -# ActiveSupport::Inflector.inflections do |inflect| +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end -# + # These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections do |inflect| +# ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' # end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb b/guides/code/getting_started/config/initializers/locale.rb index d89dac7c6a..d89dac7c6a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb +++ b/guides/code/getting_started/config/initializers/locale.rb diff --git a/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb index 969ecaad65..aaf57731be 100644 --- a/guides/code/getting_started/config/initializers/secret_token.rb +++ b/guides/code/getting_started/config/initializers/secret_token.rb @@ -2,8 +2,11 @@ # Your secret key for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! + # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. -# Make sure your secret key is kept private +# You can use `rake secret` to generate a secure secret key. + +# Make sure your secret_key_base is kept private # if you're sharing your code publicly. -Blog::Application.config.secret_key_base = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459' +Blog::Application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53' diff --git a/guides/code/getting_started/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb index 3b2ca93ab9..2e37d93799 100644 --- a/guides/code/getting_started/config/initializers/session_store.rb +++ b/guides/code/getting_started/config/initializers/session_store.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Blog::Application.config.session_store :cookie_store, key: '_blog_session' +Blog::Application.config.session_store :encrypted_cookie_store, key: '_blog_session' diff --git a/guides/code/getting_started/config/initializers/wrap_parameters.rb b/guides/code/getting_started/config/initializers/wrap_parameters.rb index 999df20181..33725e95fd 100644 --- a/guides/code/getting_started/config/initializers/wrap_parameters.rb +++ b/guides/code/getting_started/config/initializers/wrap_parameters.rb @@ -1,14 +1,14 @@ # Be sure to restart your server when you modify this file. -# + # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] + wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end -# Disable root element in JSON by default. -ActiveSupport.on_load(:active_record) do - self.include_root_in_json = false -end +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/guides/code/getting_started/config/locales/en.yml b/guides/code/getting_started/config/locales/en.yml index 179c14ca52..0653957166 100644 --- a/guides/code/getting_started/config/locales/en.yml +++ b/guides/code/getting_started/config/locales/en.yml @@ -1,5 +1,23 @@ -# Sample localization file for English. Add more files in this directory for other locales. -# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. en: hello: "Hello world" diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb index d94b0d6f33..9950568629 100644 --- a/guides/code/getting_started/config/routes.rb +++ b/guides/code/getting_started/config/routes.rb @@ -1,63 +1,7 @@ Blog::Application.routes.draw do - resources :posts do resources :comments end - - # The priority is based upon order of creation: - # first created -> highest priority. - - # Sample of regular route: - # match 'products/:id' => 'catalog#view' - # Keep in mind you can assign values other than :controller and :action - - # Sample of named route: - # match 'products/:id/purchase' => 'catalog#purchase', as: :purchase - # This route can be invoked with purchase_url(id: product.id) - - # Sample resource route (maps HTTP verbs to controller actions automatically): - # resources :products - - # Sample resource route with options: - # resources :products do - # member do - # get 'short' - # post 'toggle' - # end - # - # collection do - # get 'sold' - # end - # end - - # Sample resource route with sub-resources: - # resources :products do - # resources :comments, :sales - # resource :seller - # end - - # Sample resource route with more complex sub-resources - # resources :products do - # resources :comments - # resources :sales do - # get 'recent', on: :collection - # end - # end - - # Sample resource route within a namespace: - # namespace :admin do - # # Directs /admin/products/* to Admin::ProductsController - # # (app/controllers/admin/products_controller.rb) - # resources :products - # end - - # You can have the root of your site routed with "root" - # just remember to delete public/index.html. - root :to => "welcome#index" - - # See how all your routes lay out with "rake routes" - - # This is a legacy wild controller route that's not recommended for RESTful applications. - # Note: This route will make all actions in every controller accessible via GET requests. - # match ':controller(/:action(/:id))(.:format)' + + root to: "welcome#index" end diff --git a/guides/code/getting_started/db/migrate/20120420083127_create_posts.rb b/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb index 602bef31ab..602bef31ab 100644 --- a/guides/code/getting_started/db/migrate/20120420083127_create_posts.rb +++ b/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb diff --git a/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb b/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb index adda8078c1..3e51f9c0f7 100644 --- a/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb +++ b/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb @@ -3,10 +3,9 @@ class CreateComments < ActiveRecord::Migration create_table :comments do |t| t.string :commenter t.text :body - t.references :post + t.references :post, index: true t.timestamps end - add_index :comments, :post_id end end diff --git a/guides/code/getting_started/db/schema.rb b/guides/code/getting_started/db/schema.rb index cfb56ca9b9..101fe712a1 100644 --- a/guides/code/getting_started/db/schema.rb +++ b/guides/code/getting_started/db/schema.rb @@ -9,34 +9,25 @@ # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # -# It's strongly recommended to check this file into your version control system. +# It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(:version => 20120420083127) do +ActiveRecord::Schema.define(version: 20130122045842) do - create_table "comments", :force => true do |t| + create_table "comments", force: true do |t| t.string "commenter" t.text "body" t.integer "post_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" end - add_index "comments", ["post_id"], :name => "index_comments_on_post_id" + add_index "comments", ["post_id"], name: "index_comments_on_post_id" - create_table "posts", :force => true do |t| + create_table "posts", force: true do |t| t.string "title" t.text "text" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" end - create_table "tags", :force => true do |t| - t.string "name" - t.integer "post_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - add_index "tags", ["post_id"], :name => "index_tags_on_post_id" - end diff --git a/guides/code/getting_started/test/fixtures/.gitkeep b/guides/code/getting_started/lib/assets/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/test/fixtures/.gitkeep +++ b/guides/code/getting_started/lib/assets/.keep diff --git a/guides/code/getting_started/test/functional/.gitkeep b/guides/code/getting_started/lib/tasks/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/test/functional/.gitkeep +++ b/guides/code/getting_started/lib/tasks/.keep diff --git a/guides/code/getting_started/test/integration/.gitkeep b/guides/code/getting_started/log/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/test/integration/.gitkeep +++ b/guides/code/getting_started/log/.keep diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html index 9a48320a5f..3d875c342e 100644 --- a/guides/code/getting_started/public/404.html +++ b/guides/code/getting_started/public/404.html @@ -2,7 +2,7 @@ <html> <head> <title>The page you were looking for doesn't exist (404)</title> - <style type="text/css"> + <style> body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } div.dialog { width: 25em; @@ -22,5 +22,6 @@ <h1>The page you were looking for doesn't exist.</h1> <p>You may have mistyped the address or the page may have moved.</p> </div> + <p>If you are the application owner check the logs for more information.</p> </body> </html> diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html index 83660ab187..3f1bfb3417 100644 --- a/guides/code/getting_started/public/422.html +++ b/guides/code/getting_started/public/422.html @@ -2,7 +2,7 @@ <html> <head> <title>The change you wanted was rejected (422)</title> - <style type="text/css"> + <style> body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } div.dialog { width: 25em; diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html index f3648a0dbc..012977d3d2 100644 --- a/guides/code/getting_started/public/500.html +++ b/guides/code/getting_started/public/500.html @@ -2,7 +2,7 @@ <html> <head> <title>We're sorry, but something went wrong (500)</title> - <style type="text/css"> + <style> body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } div.dialog { width: 25em; @@ -21,5 +21,6 @@ <div class="dialog"> <h1>We're sorry, but something went wrong.</h1> </div> + <p>If you are the application owner check the logs for more information.</p> </body> </html> diff --git a/guides/code/getting_started/script/rails b/guides/code/getting_started/script/rails deleted file mode 100755 index f8da2cffd4..0000000000 --- a/guides/code/getting_started/script/rails +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. - -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands' diff --git a/guides/code/getting_started/test/unit/.gitkeep b/guides/code/getting_started/test/controllers/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/test/unit/.gitkeep +++ b/guides/code/getting_started/test/controllers/.keep diff --git a/guides/code/getting_started/test/functional/comments_controller_test.rb b/guides/code/getting_started/test/controllers/comments_controller_test.rb index 2ec71b4ec5..2ec71b4ec5 100644 --- a/guides/code/getting_started/test/functional/comments_controller_test.rb +++ b/guides/code/getting_started/test/controllers/comments_controller_test.rb diff --git a/guides/code/getting_started/test/unit/tag_test.rb b/guides/code/getting_started/test/controllers/posts_controller_test.rb index b8498a117c..7a6ee4f1db 100644 --- a/guides/code/getting_started/test/unit/tag_test.rb +++ b/guides/code/getting_started/test/controllers/posts_controller_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class TagTest < ActiveSupport::TestCase +class PostsControllerTest < ActionController::TestCase # test "the truth" do # assert true # end diff --git a/guides/code/getting_started/test/functional/welcome_controller_test.rb b/guides/code/getting_started/test/controllers/welcome_controller_test.rb index e4d5abae11..dff8e9d2c5 100644 --- a/guides/code/getting_started/test/functional/welcome_controller_test.rb +++ b/guides/code/getting_started/test/controllers/welcome_controller_test.rb @@ -5,4 +5,5 @@ class WelcomeControllerTest < ActionController::TestCase get :index assert_response :success end + end diff --git a/guides/code/getting_started/vendor/plugins/.gitkeep b/guides/code/getting_started/test/fixtures/.keep index e69de29bb2..e69de29bb2 100644 --- a/guides/code/getting_started/vendor/plugins/.gitkeep +++ b/guides/code/getting_started/test/fixtures/.keep diff --git a/guides/code/getting_started/test/fixtures/comments.yml b/guides/code/getting_started/test/fixtures/comments.yml index d33da386bf..0cd36069e4 100644 --- a/guides/code/getting_started/test/fixtures/comments.yml +++ b/guides/code/getting_started/test/fixtures/comments.yml @@ -1,11 +1,11 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html one: commenter: MyString body: MyText - post: + post_id: two: commenter: MyString body: MyText - post: + post_id: diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/posts.yml index e1edfd385e..617a24b858 100644 --- a/guides/code/getting_started/test/fixtures/posts.yml +++ b/guides/code/getting_started/test/fixtures/posts.yml @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html one: title: MyString diff --git a/guides/code/getting_started/test/functional/posts_controller_test.rb b/guides/code/getting_started/test/functional/posts_controller_test.rb deleted file mode 100644 index b8f7b07820..0000000000 --- a/guides/code/getting_started/test/functional/posts_controller_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' - -class PostsControllerTest < ActionController::TestCase - setup do - @post = posts(:one) - end - - test "should get index" do - get :index - assert_response :success - assert_not_nil assigns(:posts) - end - - test "should get new" do - get :new - assert_response :success - end - - test "should create post" do - assert_difference('Post.count') do - post :create, post: @post.attributes - end - - assert_redirected_to post_path(assigns(:post)) - end - - test "should show post" do - get :show, id: @post.to_param - assert_response :success - end - - test "should get edit" do - get :edit, id: @post.to_param - assert_response :success - end - - test "should update post" do - put :update, id: @post.to_param, post: @post.attributes - assert_redirected_to post_path(assigns(:post)) - end - - test "should destroy post" do - assert_difference('Post.count', -1) do - delete :destroy, id: @post.to_param - end - - assert_redirected_to posts_path - end -end diff --git a/guides/code/getting_started/test/helpers/.keep b/guides/code/getting_started/test/helpers/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/guides/code/getting_started/test/helpers/.keep diff --git a/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb b/guides/code/getting_started/test/helpers/comments_helper_test.rb index 2518c16bd5..2518c16bd5 100644 --- a/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb +++ b/guides/code/getting_started/test/helpers/comments_helper_test.rb diff --git a/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb b/guides/code/getting_started/test/helpers/posts_helper_test.rb index 48549c2ea1..48549c2ea1 100644 --- a/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb +++ b/guides/code/getting_started/test/helpers/posts_helper_test.rb diff --git a/guides/code/getting_started/test/helpers/welcome_helper_test.rb b/guides/code/getting_started/test/helpers/welcome_helper_test.rb new file mode 100644 index 0000000000..d6ded5995f --- /dev/null +++ b/guides/code/getting_started/test/helpers/welcome_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class WelcomeHelperTest < ActionView::TestCase +end diff --git a/guides/code/getting_started/test/integration/.keep b/guides/code/getting_started/test/integration/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/guides/code/getting_started/test/integration/.keep diff --git a/guides/code/getting_started/test/mailers/.keep b/guides/code/getting_started/test/mailers/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/guides/code/getting_started/test/mailers/.keep diff --git a/guides/code/getting_started/test/models/.keep b/guides/code/getting_started/test/models/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/guides/code/getting_started/test/models/.keep diff --git a/guides/code/getting_started/test/unit/comment_test.rb b/guides/code/getting_started/test/models/comment_test.rb index b6d6131a96..b6d6131a96 100644 --- a/guides/code/getting_started/test/unit/comment_test.rb +++ b/guides/code/getting_started/test/models/comment_test.rb diff --git a/guides/code/getting_started/test/unit/post_test.rb b/guides/code/getting_started/test/models/post_test.rb index 6d9d463a71..6d9d463a71 100644 --- a/guides/code/getting_started/test/unit/post_test.rb +++ b/guides/code/getting_started/test/models/post_test.rb diff --git a/guides/code/getting_started/test/test_helper.rb b/guides/code/getting_started/test/test_helper.rb index 3daca18a71..f91a4375dc 100644 --- a/guides/code/getting_started/test/test_helper.rb +++ b/guides/code/getting_started/test/test_helper.rb @@ -3,6 +3,8 @@ require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' class ActiveSupport::TestCase + ActiveRecord::Migration.check_pending! + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests diff --git a/guides/code/getting_started/test/unit/helpers/home_helper_test.rb b/guides/code/getting_started/test/unit/helpers/home_helper_test.rb deleted file mode 100644 index 4740a18dac..0000000000 --- a/guides/code/getting_started/test/unit/helpers/home_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class HomeHelperTest < ActionView::TestCase -end diff --git a/guides/code/getting_started/vendor/assets/javascripts/.keep b/guides/code/getting_started/vendor/assets/javascripts/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/guides/code/getting_started/vendor/assets/javascripts/.keep diff --git a/guides/code/getting_started/vendor/assets/stylesheets/.keep b/guides/code/getting_started/vendor/assets/stylesheets/.keep new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/guides/code/getting_started/vendor/assets/stylesheets/.keep diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 52571fab60..9c157ec0b3 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -3,11 +3,10 @@ Ruby on Rails 4.0 Release Notes Highlights in Rails 4.0: -* Ruby 1.9.3 only +* Ruby 2.0 preferred; 1.9.3+ required * Strong Parameters * Turbolinks * Russian Doll Caching -* Asynchronous Mailers These release notes cover only the major changes. To know about various bug fixes and changes, please refer to the change logs or check out the [list of commits](https://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. @@ -150,7 +149,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ Action Pack ----------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionpack/CHANGELOG.md) for detailed changes. ### Notable changes @@ -162,7 +161,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt Active Record ------------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activerecord/CHANGELOG.md) for detailed changes. ### Notable changes diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index cc80334af3..7260a48c8c 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -58,7 +58,7 @@ Parameters You will probably want to access data sent in by the user or other parameters in your controller actions. There are two kinds of parameters possible in a web application. The first are parameters that are sent as part of the URL, called query string parameters. The query string is everything after "?" in the URL. The second type of parameter is usually referred to as POST data. This information usually comes from an HTML form which has been filled in by the user. It's called POST data because it can only be sent as part of an HTTP POST request. Rails does not make any distinction between query string parameters and POST parameters, and both are available in the `params` hash in your controller: ```ruby -class ClientsController < ActionController::Base +class ClientsController < ApplicationController # This action uses query string parameters because it gets run # by an HTTP GET request, but this does not make any difference # to the way in which the parameters are accessed. The URL for @@ -479,7 +479,7 @@ In addition to "before" filters, you can also run filters after an action has be For example, in a website where changes have an approval workflow an administrator could be able to preview them easily, just apply them within a transaction: ```ruby -class ChangesController < ActionController::Base +class ChangesController < ApplicationController around_action :wrap_in_transaction, only: :show private diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 7c6ef52f4a..513ae1272f 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -575,3 +575,25 @@ end ``` In the test we send the email and store the returned object in the `email` variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect. + +NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in `ActionMailer::TestCase` tests. If you want to have a clean slate outside Action Mailer tests, you can reset it manually with: `ActionMailer::Base.deliveries.clear` + +Intercepting Emails +------------------- +There are situations where you need to edit an email before it's delivered. Fortunately Action Mailer provides hooks to intercept every email. You can register an interceptor to make modifications to mail messages right before they are handed to the delivery agents. + +```ruby +class SandboxEmailInterceptor + def self.delivering_email(message) + message.to = ['sandbox@example.com'] + end +end +``` + +Before the interceptor can do its job you need to register it with the Action Mailer framework. You can do this in an initializer file `config/initializers/sandbox_email_interceptor.rb` + +```ruby +ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging? +``` + +NOTE: The example above uses a custom environment called "staging" for a production like server but for testing purposes. You can read [Creating Rails environments](./configuring.html#creating-rails-environments) for more information about custom Rails environments. diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 062bcd49f4..69d7333e6f 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -1,6 +1,6 @@ Active Record Basics ==================== - + This guide is an introduction to Active Record. After reading this guide, you will know: diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 3747b00b82..516457bcd3 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -157,7 +157,6 @@ The following methods trigger callbacks: * `save!` * `save(validate: false)` * `toggle!` -* `update` * `update_attribute` * `update` * `update!` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 3c1bb0f132..f02b377832 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1233,6 +1233,8 @@ The method `squish` strips leading and trailing whitespace, and substitutes runs There's also the destructive version `String#squish!`. +Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). + NOTE: Defined in `active_support/core_ext/string/filters.rb`. ### `truncate` diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index fffa31927d..e939606c88 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -37,9 +37,9 @@ You should use the defaults for all new applications unless you have a specific ### Main Features -The first feature of the pipeline is to concatenate assets. This is important in a production environment, because it can reduce the number of requests that a browser must make to render a web page. Web browsers are limited in the number of requests that they can make in parallel, so fewer requests can mean faster loading for your application. +The first feature of the pipeline is to concatenate assets. This is important in a production environment, because it can reduce the number of requests that a browser makes to render a web page. Web browsers are limited in the number of requests that they can make in parallel, so fewer requests can mean faster loading for your application. -Rails 2.x introduced the ability to concatenate JavaScript and CSS assets by placing `:cache => true` at the end of the `javascript_include_tag` and `stylesheet_link_tag` methods. But this technique has some limitations. For example, it cannot generate the caches in advance, and it is not able to transparently include assets provided by third-party libraries. +Rails 2.x introduced the ability to concatenate JavaScript and CSS assets by placing `cache: true` at the end of the `javascript_include_tag` and `stylesheet_link_tag` methods. But this technique has some limitations. For example, it cannot generate the caches in advance, and it is not able to transparently include assets provided by third-party libraries. Starting with version 3.1, Rails defaults to concatenating all JavaScript files into one master `.js` file and all CSS files into one master `.css` file. As you'll learn later in this guide, you can customize this strategy to group files any way you like. In production, Rails inserts an MD5 fingerprint into each filename so that the file is cached by the web browser. You can invalidate the cache by altering this fingerprint, which happens automatically whenever you change the file contents. @@ -369,8 +369,8 @@ If any of the files in the manifest have changed between requests, the server re Debug mode can also be enabled in the Rails helper methods: ```erb -<%= stylesheet_link_tag "application", :debug => true %> -<%= javascript_include_tag "application", :debug => true %> +<%= stylesheet_link_tag "application", debug: true %> +<%= javascript_include_tag "application", debug: true %> ``` The `:debug` option is redundant if debug mode is on. @@ -445,7 +445,7 @@ NOTE. If you are precompiling your assets locally, you can use `bundle install - The default matcher for compiling files includes `application.js`, `application.css` and all non-JS/CSS files (this will include all image assets automatically): ```ruby -[ Proc.new{ |path| !%w(.js .css).include?(File.extname(path)) }, /application.(css|js)$/ ] +[ Proc.new { |path| !%w(.js .css).include?(File.extname(path)) }, /application.(css|js)$/ ] ``` NOTE. The matcher (and other members of the precompile array; see below) is applied to final compiled file names. This means that anything that compiles to JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and `.scss` files are **not** automatically included as they compile to JS/CSS. @@ -460,7 +460,7 @@ Or you can opt to precompile all assets with something like this: ```ruby # config/environments/production.rb -config.assets.precompile << Proc.new { |path| +config.assets.precompile << Proc.new do |path| if path =~ /\.(css|js)\z/ full_path = Rails.application.assets.resolve(path).to_path app_assets_path = Rails.root.join('app', 'assets').to_path @@ -474,7 +474,7 @@ config.assets.precompile << Proc.new { |path| else false end -} +end ``` NOTE. Always specify an expected compiled filename that ends with js or css, even if you want to add Sass or CoffeeScript files to the precompile array. @@ -502,14 +502,14 @@ For Apache: ```apache # The Expires* directives requires the Apache module `mod_expires` to be enabled. -<LocationMatch "^/assets/.*$"> +<Location /assets/> # Use of ETag is discouraged when Last-Modified is present Header unset ETag FileETag None # RFC says only cache for 1 year ExpiresActive On ExpiresDefault "access plus 1 year" -</LocationMatch> +</Location> ``` For nginx: @@ -663,7 +663,7 @@ class Transformer end ``` -To enable this, pass a `new` object to the config option in `application.rb`: +To enable this, pass a new object to the config option in `application.rb`: ```ruby config.assets.css_compressor = Transformer.new diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 0228d463cf..a270ec7a7e 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -85,6 +85,51 @@ This fragment is then available to all actions in the `ProductsController` using ```ruby expire_fragment('all_available_products') ``` +If you want to avoid expiring the fragment manually, whenever an action updates a product, you can define a helper method: + +```ruby +module ProductsHelper + def cache_key_for_products + count = Product.count + max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number) + "products/all-#{count}-#{max_updated_at}" + end +end +``` + +This method generates a cache key that depends on all products and can be used in the view: + +```erb +<% cache(cache_key_for_products) do %> + All available products: +<% end %> +``` +You can also use an Active Record model as the cache key: + +```erb +<% Product.all.each do |p| %> + <% cache(p) do %> + <%= link_to p.name, product_url(p) %> + <% end %> +<% end %> +``` + +Behind the scenes, a method called `cache_key` will be invoked on the model and it returns a string like `products/23-20130109142513`. The cache key includes the model name, the id and finally the updated_at timestamp. Thus it will automatically generate a new fragment when the product is updated because the key changes. + +You can also combine the two schemes which is called "Russian Doll Caching": + +```erb +<% cache(cache_key_for_products) do %> + All available products: + <% Product.all.each do |p| %> + <% cache(p) do %> + <%= link_to p.name, product_url(p) %> + <% end %> + <% end %> +<% end %> +``` + +It's called "Russian Doll Caching" because it nests multiple fragments. The advantage is that if a single product is updated, all the other inner fragments can be reused when regenerating the outer fragment. ### SQL Caching @@ -93,7 +138,7 @@ Query caching is a Rails feature that caches the result set returned by each que For example: ```ruby -class ProductsController < ActionController +class ProductsController < ApplicationController def index # Run a find query diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 12d39ea1cc..9d1fb03fab 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -355,7 +355,7 @@ rake assets:clean # Remove compiled assets rake assets:precompile # Compile all the assets named in config.assets.precompile rake db:create # Create the database from config/database.yml for the current Rails.env ... -rake log:clear # Truncates all *.log files in log/ to zero bytes +rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development) rake middleware # Prints out your Rack middleware stack ... rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear) @@ -378,7 +378,7 @@ Active Record version 4.0.0.beta Action Pack version 4.0.0.beta Action Mailer version 4.0.0.beta Active Support version 4.0.0.beta -Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::EncryptedCookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport +Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::EncryptedCookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 @@ -442,7 +442,7 @@ app/model/post.rb: NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines. -By default, `rake notes` will look in the `app`, `config`, `lib`, `script` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`. +By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`. ```bash $ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor' diff --git a/guides/source/configuring.md b/guides/source/configuring.md index cd0b99b177..be46e15078 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -207,7 +207,6 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::ParamsParser` parses out parameters from the request into `params`. * `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types. * `ActionDispatch::Head` converts HEAD requests to GET requests and serves them as so. -* `ActionDispatch::BestStandardsSupport` enables "best standards support" so that IE8 renders some elements correctly. Besides these usual middleware, you can add your own by using the `config.middleware.use` method: @@ -230,13 +229,13 @@ config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns Middlewares can also be completely swapped out and replaced with others: ```ruby -config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns +config.middleware.swap ActionController::Failsafe, Lifo::Failsafe ``` They can also be removed from the stack completely: ```ruby -config.middleware.delete ActionDispatch::BestStandardsSupport +config.middleware.delete "Rack::MethodOverride" ``` ### Configuring i18n @@ -303,7 +302,7 @@ The schema dumper adds one additional configuration option: * `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`. -* `config.action_controller.raise_on_unpermitted_parameters` enables raising an exception if parameters that are not explicitly permitted are found. The default value is `true` in development and test environments, `false` otherwise. +* `config.action_controller.action_on_unpermitted_params` enables logging or raising an exception if parameters that are not explicitly permitted are found. Set to `:log` or `:raise` to enable. The default value is `:log` in development and test environments, and `false` in all other environments. ### Configuring Action Dispatch @@ -345,34 +344,6 @@ The schema dumper adds one additional configuration option: * `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`. See the [ERB documentation](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/) for more information. -* `config.action_view.javascript_expansions` is a hash containing expansions that can be used for the JavaScript include tag. By default, this is defined as: - - ```ruby - config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } - ``` - - However, you may add to this by defining others: - - ```ruby - config.action_view.javascript_expansions[:prototype] = [ - 'prototype', 'effects', 'dragdrop', 'controls' - ] - ``` - - And can reference in the view with the following code: - - ```ruby - <%= javascript_include_tag :prototype %> - ``` - -* `config.action_view.stylesheet_expansions` works in much the same way as `javascript_expansions`, but has no default key. Keys defined for this hash can be referenced in the view like such: - - ```ruby - <%= stylesheet_link_tag :special %> - ``` - -* `config.action_view.cache_asset_ids` With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running. - * `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true` * `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::PostsController` which renders this template: @@ -552,6 +523,15 @@ development: Change the username and password in the `development` section as appropriate. +### Creating Rails Environments + +By default Rails ships with three environments: "development", "test", and "production". While these are sufficient for most use cases, there are circumstances when you want more environments. + +Imagine you have a server which mirrors the production environment but is only used for testing. Such a server is commonly called a "staging server". To define an environment called "staging" for this server just by create a file called `config/environments/staging.rb`. Please use the contents of any existing file in `config/environments` as a starting point and make the necessary changes from there. + +That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console staging`, `Rails.env.staging?` works, etc. + + Rails Environment Settings -------------------------- @@ -672,10 +652,6 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`. -* `action_view.cache_asset_ids` Sets `ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids` to `false` when Active Support loads, but only if `config.cache_classes` is too. - -* `action_view.javascript_expansions` Registers the expansions set up by `config.action_view.javascript_expansions` and `config.action_view.stylesheet_expansions` to be recognized by Action View and therefore usable in the views. - * `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. * `action_controller.logger` Sets `ActionController::Base.logger` — if it's not already set — to `Rails.logger`. diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index db43d62fcf..6493c1e1ec 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -174,6 +174,20 @@ $ cd activerecord $ bundle exec rake postgresql:build_databases ``` +It is possible to build databases for both PostgreSQL and MySQL with + +```bash +$ cd activerecord +$ bundle exec rake db:create +``` + +You can cleanup the databases using + +```bash +$ cd activerecord +$ bundle exec rake db:drop +``` + NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation. NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator". diff --git a/guides/source/engines.md b/guides/source/engines.md index f35233993c..00939c4ff2 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -28,7 +28,7 @@ Engines can also be isolated from their host applications. This means that an ap It's important to keep in mind at all times that the application should **always** take precedence over its engines. An application is the object that has final say in what goes on in the universe (with the universe being the application's environment) where the engine should only be enhancing it, rather than changing it drastically. -To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or [Forem](https://github.com/radar/forem), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and [RefineryCMS](https://github.com/resolve/refinerycms), a CMS engine. +To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or [Forem](https://github.com/radar/forem), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and [RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine. Finally, engines would not have been possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever meet them, don't forget to say thanks! @@ -57,7 +57,7 @@ The `--full` option tells the generator that you want to create an engine, inclu end ``` * A file at `lib/blorgh/engine.rb` which is identical in function to a standard Rails application's `config/application.rb` file: - + ```ruby module Blorgh class Engine < ::Rails::Engine @@ -72,12 +72,12 @@ The `--mountable` option tells the generator that you want to create a "mountabl * A namespaced `ApplicationHelper` stub * A layout view template for the engine * Namespace isolation to `config/routes.rb`: - + ```ruby Blorgh::Engine.routes.draw do end ``` - + * Namespace isolation to `lib/blorgh/engine.rb`: ```ruby @@ -261,9 +261,9 @@ end This helps prevent conflicts with any other engine or application that may have a post resource as well. -Finally, two files that are the assets for this resource are generated, `app/assets/javascripts/blorgh/posts.js` and `app/assets/javascripts/blorgh/posts.css`. You'll see how to use these a little later. +Finally, two files that are the assets for this resource are generated, `app/assets/javascripts/blorgh/posts.js` and `app/assets/stylesheets/blorgh/posts.css`. You'll see how to use these a little later. -By default, the scaffold styling is not applied to the engine as the engine's layout file, `app/views/blorgh/application.html.erb` doesn't load it. To make this apply, insert this line into the `<head>` tag of this layout: +By default, the scaffold styling is not applied to the engine as the engine's layout file, `app/views/layouts/blorgh/application.html.erb` doesn't load it. To make this apply, insert this line into the `<head>` tag of this layout: ```erb <%= stylesheet_link_tag "scaffold" %> @@ -288,7 +288,7 @@ Now people will only need to go to the root of the engine to see all the posts, ### Generating a comments resource -Now that the engine can to create new blog posts, it only makes sense to add commenting functionality as well. To do get this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones. +Now that the engine can create new blog posts, it only makes sense to add commenting functionality as well. To do this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones. Run the model generator and tell it to generate a `Comment` model, with the related table having two columns: a `post_id` integer and `text` text column. @@ -395,7 +395,7 @@ def create @post = Post.find(params[:post_id]) @comment = @post.comments.create(params[:comment]) flash[:notice] = "Comment has been created!" - redirect_to post_path + redirect_to posts_path end ``` @@ -470,7 +470,7 @@ If you have multiple engines that need migrations copied over, use `railties:ins $ rake railties:install:migrations ``` -This command, when run for the first time will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this: +This command, when run for the first time, will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this: ```bash Copied migration [timestamp_1]_create_blorgh_posts.rb from blorgh @@ -650,6 +650,14 @@ self.author = Blorgh.user_class.find_or_create_by(name: author_name) Resulting in something a little shorter, and more implicit in its behavior. The `user_class` method should always return a `Class` object. +Since we changed the `user_class` method to no longer return a +`String` but a `Class` we must also modify our `belongs_to` definition +in the `Blorgh::Post` model: + +```ruby +belongs_to :author, class_name: Blorgh.user_class.to_s +``` + To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine's models which may depend on this configuration setting existing. Create a new initializer at `config/initializers/blorgh.rb` inside the application where the `blorgh` engine is installed and put this content in it: @@ -754,10 +762,9 @@ end #### Implementing Decorator Pattern Using ActiveSupport::Concern -Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`](http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html) helps manage load order of interlinked dependencies at run time allowing you to significantly modularize your code. +Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`](http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html). ActiveSupport::Concern manages load order of interlinked dependent modules and classes at run time allowing you to significantly modularize your code. -**Adding** `Post#time_since_created`<br/> -**Overriding** `Post#summary` +**Adding** `Post#time_since_created` and **Overriding** `Post#summary` ```ruby # MyApp/app/models/blorgh/post.rb @@ -790,7 +797,7 @@ module Blorgh::Concerns::Models::Post extend ActiveSupport::Concern # 'included do' causes the included code to be evaluated in the - # conext where it is included (post.rb), rather than be + # context where it is included (post.rb), rather than be # executed in the module's context (blorgh/concerns/models/post). included do attr_accessor :author_name @@ -800,9 +807,9 @@ module Blorgh::Concerns::Models::Post private - def set_author - self.author = User.find_or_create_by(name: author_name) - end + def set_author + self.author = User.find_or_create_by(name: author_name) + end end def summary @@ -840,7 +847,7 @@ Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` a ### Routes -Routes inside an engine are, by default, isolated from the application. This is done by the `isolate_namespace` call inside the `Engine` class. This essentially means that the application and its engines can have identically named routes, and that they will not clash. +Routes inside an engine are, by default, isolated from the application. This is done by the `isolate_namespace` call inside the `Engine` class. This essentially means that the application and its engines can have identically named routes and they will not clash. Routes inside an engine are drawn on the `Engine` class within `config/routes.rb`, like this: diff --git a/guides/source/generators.md b/guides/source/generators.md index 8b91dfc5a5..1a08eb420a 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -310,7 +310,7 @@ In Rails 3.0 and above, generators don't just look in the source root for templa ```erb module <%= class_name %>Helper - attr_reader :<%= plural_name %>, <%= plural_name.singularize %> + attr_reader :<%= plural_name %>, :<%= plural_name.singularize %> end ``` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 7d86b3866a..87f5e43157 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -21,7 +21,7 @@ application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or higher +* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer * The [RubyGems](http://rubygems.org/) packaging system * To learn more about RubyGems, please read the [RubyGems User Guide](http://docs.rubygems.org/read/book/1) * A working installation of the [SQLite3 Database](http://www.sqlite.org) @@ -84,7 +84,7 @@ current version of Ruby installed: ```bash $ ruby -v -ruby 1.9.3p327 +ruby 1.9.3p385 ``` To install Rails, use the `gem install` command provided by RubyGems: @@ -583,9 +583,31 @@ content: </p> ``` -Finally, if you now go to -<http://localhost:3000/posts/new> you'll -be able to create a post. Try it! +If you now go to +<http://localhost:3000/posts/new> you'll *almost* be able to create a post. Try +it! You should get an error that looks like this: + +![Forbidden attributes for new post](images/getting_started/forbidden_attributes_for_new_post.png) + +Rails has several security features that help you write secure applications, +and you're running into one of them now. This one is called +'strong_parameters,' which requires us to tell Rails exactly which parameters +we want to accept in our controllers. In this case, we want to allow the +'title' and 'text' parameters, so change your `create` controller action to +look like this: + +``` + def create + @post = Post.new(params[:post].permit(:title, :text)) + + @post.save + redirect_to action: :show, id: @post.id + end +``` + +See the `permit`? It allows us to accept both `title` and `text` in this +action. With this change, you should finally be able to create new `Post`s. +Visit <http://localhost:3000/posts/new> and give it a try! ![Show action for posts](images/getting_started/show_action_for_posts.png) @@ -711,10 +733,11 @@ class Post < ActiveRecord::Base end ``` -These changes will ensure that all posts have a title that is at least five characters long. -Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their -format, and the existence of associated objects. Validations are covered in detail -in [Active Record Validations and Callbacks](active_record_validations_callbacks.html#validations-overview) +These changes will ensure that all posts have a title that is at least five +characters long. Rails can validate a variety of conditions in a model, +including the presence or uniqueness of columns, their format, and the +existence of associated objects. Validations are covered in detail in [Active +Record Validations](active_record_validations.html) With the validation now in place, when you call `@post.save` on an invalid post, it will return `false`. If you open `app/controllers/posts_controller.rb` @@ -729,7 +752,7 @@ def new end def create - @post = Post.new(params[:post]) + @post = Post.new(params[:post].permit(:title, :text)) if @post.save redirect_to action: :show, id: @post.id @@ -864,8 +887,8 @@ method: :patch do |f| %> This time we point the form to the `update` action, which is not defined yet but will be very soon. -The `method: :patch` option tells Rails that we want this form to be -submitted via the `PUT` HTTP method which is the HTTP method you're expected to use to +The `method: :patch` option tells Rails that we want this form to be submitted +via the `PATCH` HTTP method which is the HTTP method you're expected to use to **update** resources according to the REST protocol. TIP: By default forms built with the _form_for_ helper are sent via `POST`. @@ -883,7 +906,7 @@ And then create the `update` action in `app/controllers/posts_controller.rb`: def update @post = Post.find(params[:id]) - if @post.update(params[:post]) + if @post.update(params[:post].permit(:title, :text)) redirect_to action: :show, id: @post.id else render 'edit' @@ -1388,7 +1411,7 @@ Let's wire up the `create` in `app/controllers/comments_controller.rb`: class CommentsController < ApplicationController def create @post = Post.find(params[:post_id]) - @comment = @post.comments.create(params[:comment]) + @comment = @post.comments.create(params[:comment].permit(:commenter, :body)) redirect_to post_path(@post) end end @@ -1559,6 +1582,9 @@ Then you make the `app/views/posts/show.html.erb` look like the following: <%= @post.text %> </p> +<h2>Comments</h2> +<%= render @post.comments %> + <h2>Add a comment:</h2> <%= render "comments/form" %> diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 69232d9bd4..5304ca4285 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -102,7 +102,7 @@ The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced. -The default initializer `locale.rb` file has instructions on how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines. +The default `application.rb` files has instructions on how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines. ```ruby # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 7c26512046..339008ab9e 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -695,72 +695,6 @@ To include `http://example.com/main.js`: <%= javascript_include_tag "http://example.com/main.js" %> ``` -If the application does not use the asset pipeline, the `:defaults` option loads jQuery by default: - -```erb -<%= javascript_include_tag :defaults %> -``` - -Outputting `script` tags such as this: - -```html -<script src="/javascripts/jquery.js"></script> -<script src="/javascripts/jquery_ujs.js"></script> -``` - -These two files for jQuery, `jquery.js` and `jquery_ujs.js` must be placed inside `public/javascripts` if the application doesn't use the asset pipeline. These files can be downloaded from the [jquery-rails repository on GitHub](https://github.com/indirect/jquery-rails/tree/master/vendor/assets/javascripts) - -WARNING: If you are using the asset pipeline, this tag will render a `script` tag for an asset called `defaults.js`, which would not exist in your application unless you've explicitly created it. - -And you can in any case override the `:defaults` expansion in `config/application.rb`: - -```ruby -config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) -``` - -You can also define new defaults: - -```ruby -config.action_view.javascript_expansions[:projects] = %w(projects.js tickets.js) -``` - -And use them by referencing them exactly like `:defaults`: - -```erb -<%= javascript_include_tag :projects %> -``` - -When using `:defaults`, if an `application.js` file exists in `public/javascripts` it will be included as well at the end. - -Also, if the asset pipeline is disabled, the `:all` expansion loads every JavaScript file in `public/javascripts`: - -```erb -<%= javascript_include_tag :all %> -``` - -Note that your defaults of choice will be included first, so they will be available to all subsequently included files. - -You can supply the `:recursive` option to load files in subfolders of `public/javascripts` as well: - -```erb -<%= javascript_include_tag :all, recursive: true %> -``` - -If you're loading multiple JavaScript files, you can create a better user experience by combining multiple files into a single download. To make this happen in production, specify `cache: true` in your `javascript_include_tag`: - -```erb -<%= javascript_include_tag "main", "columns", cache: true %> -``` - -By default, the combined file will be delivered as `javascripts/all.js`. You can specify a location for the cached asset file instead: - -```erb -<%= javascript_include_tag "main", "columns", - cache: "cache/main/display" %> -``` - -You can even use dynamic paths such as `cache/#{current_site}/main/display`. - #### Linking to CSS Files with the `stylesheet_link_tag` The `stylesheet_link_tag` helper returns an HTML `<link>` tag for each source provided. @@ -797,33 +731,6 @@ By default, the `stylesheet_link_tag` creates links with `media="screen" rel="st <%= stylesheet_link_tag "main_print", media: "print" %> ``` -If the asset pipeline is disabled, the `all` option links every CSS file in `public/stylesheets`: - -```erb -<%= stylesheet_link_tag :all %> -``` - -You can supply the `:recursive` option to link files in subfolders of `public/stylesheets` as well: - -```erb -<%= stylesheet_link_tag :all, recursive: true %> -``` - -If you're loading multiple CSS files, you can create a better user experience by combining multiple files into a single download. To make this happen in production, specify `cache: true` in your `stylesheet_link_tag`: - -```erb -<%= stylesheet_link_tag "main", "columns", cache: true %> -``` - -By default, the combined file will be delivered as `stylesheets/all.css`. You can specify a location for the cached asset file instead: - -```erb -<%= stylesheet_link_tag "main", "columns", - cache: "cache/main/display" %> -``` - -You can even use dynamic paths such as `cache/#{current_site}/main/display`. - #### Linking to Images with the `image_tag` The `image_tag` helper builds an HTML `<img />` tag to the specified file. By default, files are loaded from `public/images`. diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 617e01bd15..cefbc3b829 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -474,7 +474,7 @@ class ExampleMigration < ActiveRecord::Migration t.references :category end - #add a foreign key + # add a foreign key execute <<-SQL ALTER TABLE products ADD CONSTRAINT fk_products_categories @@ -1011,7 +1011,7 @@ with foreign key constraints in the database. Although Active Record does not provide any tools for working directly with such features, the `execute` method can be used to execute arbitrary SQL. You -could also use some plugin like +could also use some gem like [foreigner](https://github.com/matthuhiggins/foreigner) which add foreign key support to Active Record (including support for dumping foreign keys in `db/schema.rb`). diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index 93d8e8dfcd..b90b3bb5fc 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -98,7 +98,7 @@ A nested model form will _only_ be built if the associated object(s) exist. This Consider the following typical RESTful controller which will prepare a new Person instance and its `address` and `projects` associations before rendering the `new` template: ```ruby -class PeopleController < ActionController:Base +class PeopleController < ApplicationController def new @person = Person.new @person.built_address diff --git a/guides/source/plugins.md b/guides/source/plugins.md index f8f04c3c67..695f25f8a9 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -86,7 +86,7 @@ Run `rake` to run the test. This test should fail because we haven't implemented Great - now you are ready to start development. -Then in `lib/yaffle.rb` require `lib/core_ext`: +Then in `lib/yaffle.rb` add `require "yaffle/core_ext"`: ```ruby # yaffle/lib/yaffle.rb diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index a6119eb433..d8477d89e3 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -137,7 +137,6 @@ use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag -use ActionDispatch::BestStandardsSupport run MyApp::Application.routes ``` @@ -215,7 +214,6 @@ And to remove browser related middleware, ```ruby # config/application.rb -config.middleware.delete "ActionDispatch::BestStandardsSupport" config.middleware.delete "Rack::MethodOverride" ``` @@ -307,10 +305,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Adds ETag header on all String bodies. ETags are used to validate cache. - **`ActionDispatch::BestStandardsSupport`** - -* Enables “best standards support” so that IE8 renders some elements correctly. - TIP: It's possible to use any of the above middlewares in your custom Rack stack. ### Using Rack Builder diff --git a/guides/source/routing.md b/guides/source/routing.md index 14f23d4020..4614169653 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -832,6 +832,19 @@ will recognize incoming paths beginning with `/photos` but route to the `Images` NOTE: Use `photos_path`, `new_photo_path`, etc. to generate paths for this resource. +For namespaced controllers you can use the directory notation. For example: + +```ruby +resources :user_permissions, controller: 'admin/user_permissions' +``` + +This will route to the `Admin::UserPermissions` controller. + +NOTE: Only the directory notation is supported. specifying the +controller with ruby constant notation (eg. `:controller => +'Admin::UserPermissions'`) can lead to routing problems and results in +a warning. + ### Specifying Constraints You can use the `:constraints` option to specify a required format on the implicit `id`. For example: diff --git a/guides/source/security.md b/guides/source/security.md index 3706a61431..769bd130be 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -9,7 +9,6 @@ After reading this guide, you will know: * The concept of sessions in Rails, what to put in there and popular attack methods. * How just visiting a site can be a security problem (with CSRF). * What you have to pay attention to when working with files or providing an administration interface. -* The Rails-specific mass assignment problem. * How to manage users: Logging in and out and attack methods on all layers. * And the most popular injection attack methods. diff --git a/guides/source/testing.md b/guides/source/testing.md index 09d6d2d5ee..39a44794a7 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -943,7 +943,7 @@ Cheers! This is the right time to understand a little more about writing tests for your mailers. The line `ActionMailer::Base.delivery_method = :test` in `config/environments/test.rb` sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (`ActionMailer::Base.deliveries`). -However often in unit tests, mails will not actually be sent, simply constructed, as in the example above, where the precise content of the email is checked against what it should be. +This way, emails are not actually sent, simply constructed. The precise content of the email can then be checked against what is expected, as in the example above. ### Functional Testing diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index b4a59fe3da..568767d9de 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -16,11 +16,11 @@ The best way to be sure that your application still works after upgrading is to Rails generally stays close to the latest released Ruby version when it's released: -* Rails 3 and above requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. -* Rails 3.2.x will be the last branch to support Ruby 1.8.7. -* Rails 4 will support only Ruby 1.9.3. +* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible. +* Rails 3.2.x is the last branch to support Ruby 1.8.7. +* Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer. -TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing. +TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing. Upgrading from Rails 3.2 to Rails 4.0 ------------------------------------- @@ -82,6 +82,8 @@ becomes get 'こんにちは', controller: 'welcome', action: 'index' ``` +* Rails 4.0 has removed ActionDispatch::BestStandardsSupport middleware, !DOCTYPE html already triggers standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx and ChromeFrame header has been moved to `config.action_dispatch.default_headers` + ### Active Support Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`. diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index a7ca531123..03ef770352 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -51,7 +51,7 @@ with an id of `results`. Rails provides quite a bit of built-in support for building web pages with this technique. You rarely have to write this code yourself. The rest of this guide -will show you how Rails can help you write web sites in this manner, but it's +will show you how Rails can help you write websites in this way, but it's all built on top of this fairly simple technique. Unobtrusive JavaScript diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 4f18f4cdd2..997858b3c5 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,49 @@ ## Rails 4.0.0 (unreleased) ## +* Add notice message for destroy action in scaffold generator. + + *Rahul P. Chaudhari* + +* Add two new test rake tasks to speed up full test runs. + + * `test:all`: run tests quickly by merging all types and not resetting db. + * `test:all:db`: run tests quickly, but also reset db. + + *Ryan Davis* + +* Add `--rc` option to support the load of a custom rc file during the generation of a new app. + + *Amparo Luna* + +* Add `--no-rc` option to skip the loading of railsrc file during the generation of a new app. + + *Amparo Luna* + +* Fixes database.yml when creating a new rails application with '.' + Fix #8304 + + *Jeremy W. Rowe* + +* Deprecate the `eager_load_paths` configuration and alias it to `autoload_paths`. + Since the default in Rails 4.0 is to run in 'threadsafe' mode we need to eager + load all of the paths in `autoload_paths`. This may have unintended consequences + if you have added 'lib' to `autoload_paths` such as loading unneeded code or + code intended only for development and/or test environments. If this applies to + your application you should thoroughly check what is being eager loaded. + + *Andrew White* + +* Restore Rails::Engine::Railties#engines with deprecation to ensure + compatibility with gems such as Thinking Sphinx + Fix #8551 + + *Tim Raymond* + +* Specify which logs to clear when using the `rake log:clear` task. + (e.g. rake log:clear LOGS=test,staging) + + *Matt Bridges* + * Allow a `:dirs` key in the `SourceAnnotationExtractor.enumerate` options to explicitly set the directories to be traversed so it's easier to define custom rake tasks. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index cff75872b2..5af7de720c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -55,6 +55,7 @@ module Rails autoload :Bootstrap, 'rails/application/bootstrap' autoload :Configuration, 'rails/application/configuration' autoload :Finisher, 'rails/application/finisher' + autoload :Railties, 'rails/engine/railties' autoload :RoutesReloader, 'rails/application/routes_reloader' class << self @@ -232,11 +233,6 @@ module Rails config.helpers_paths end - def railties #:nodoc: - @railties ||= Rails::Railtie.subclasses.map(&:instance) + - Rails::Engine.subclasses.map(&:instance) - end - protected alias :build_middleware_stack :app @@ -369,10 +365,6 @@ module Rails middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" - - if config.action_dispatch.best_standards_support - middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support - end end end diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb index 2d9708e5b5..2ff29418c6 100644 --- a/railties/lib/rails/commands/application.rb +++ b/railties/lib/rails/commands/application.rb @@ -9,12 +9,19 @@ if ARGV.first != "new" ARGV[0] = "--help" else ARGV.shift - railsrc = File.join(File.expand_path("~"), ".railsrc") - if File.exist?(railsrc) - extra_args_string = File.open(railsrc).read - extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten - puts "Using #{extra_args.join(" ")} from #{railsrc}" - ARGV.insert(1, *extra_args) + unless ARGV.delete("--no-rc") + customrc = ARGV.index{ |x| x.include?("--rc=") } + railsrc = if customrc + File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, "")) + else + File.join(File.expand_path("~"), '.railsrc') + end + if File.exist?(railsrc) + extra_args_string = File.read(railsrc) + extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten + puts "Using #{extra_args.join(" ")} from #{railsrc}" + ARGV.insert(1, *extra_args) + end end end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 5fa7f043c6..15d13d5f28 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -27,11 +27,11 @@ module Rails # # Middlewares can also be completely swapped out and replaced with others: # - # config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns + # config.middleware.swap ActionDispatch::Flash, Magical::Unicorns # # And finally they can also be removed from the stack completely: # - # config.middleware.delete ActionDispatch::BestStandardsSupport + # config.middleware.delete ActionDispatch::Flash # class MiddlewareStackProxy def initialize diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 0ae2f16aba..8a6d1f34aa 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -34,9 +34,8 @@ module Rails # == Configuration # # Besides the +Railtie+ configuration which is shared across the application, in a - # <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt>, <tt>eager_load_paths</tt> - # and <tt>autoload_once_paths</tt>, which, differently from a <tt>Railtie</tt>, are scoped to - # the current engine. + # <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt> and <tt>autoload_once_paths</tt>, + # which, differently from a <tt>Railtie</tt>, are scoped to the current engine. # # class MyEngine < Rails::Engine # # Add a load path for this specific Engine @@ -456,9 +455,9 @@ module Rails end # Eager load the application by loading all ruby - # files inside eager_load paths. + # files inside autoload_paths. def eager_load! - config.eager_load_paths.each do |load_path| + config.autoload_paths.each do |load_path| matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/ Dir.glob("#{load_path}/**/*.rb").sort.each do |file| require_dependency file.sub(matcher, '\1') @@ -466,6 +465,10 @@ module Rails end end + def railties + @railties ||= self.class::Railties.new + end + # Returns a module with all the helpers defined for the engine. def helpers @helpers ||= begin @@ -554,7 +557,6 @@ module Rails # Freeze so future modifications will fail rather than do nothing mysteriously config.autoload_paths.freeze - config.eager_load_paths.freeze config.autoload_once_paths.freeze end @@ -667,7 +669,7 @@ module Rails end def _all_autoload_paths #:nodoc: - @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq + @_all_autoload_paths ||= (config.autoload_paths + config.autoload_once_paths).uniq end def _all_load_paths #:nodoc: diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 10d1821709..2b23d8be38 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -4,7 +4,7 @@ module Rails class Engine class Configuration < ::Rails::Railtie::Configuration attr_reader :root - attr_writer :middleware, :eager_load_paths, :autoload_once_paths, :autoload_paths + attr_writer :middleware, :autoload_once_paths, :autoload_paths def initialize(root=nil) super() @@ -39,16 +39,16 @@ module Rails @paths ||= begin paths = Rails::Paths::Root.new(@root) - paths.add "app", eager_load: true, glob: "*" + paths.add "app", autoload: true, glob: "*" paths.add "app/assets", glob: "*" - paths.add "app/controllers", eager_load: true - paths.add "app/helpers", eager_load: true - paths.add "app/models", eager_load: true - paths.add "app/mailers", eager_load: true + paths.add "app/controllers", autoload: true + paths.add "app/helpers", autoload: true + paths.add "app/models", autoload: true + paths.add "app/mailers", autoload: true paths.add "app/views" - paths.add "app/controllers/concerns", eager_load: true - paths.add "app/models/concerns", eager_load: true + paths.add "app/controllers/concerns", autoload: true + paths.add "app/models/concerns", autoload: true paths.add "lib", load_path: true paths.add "lib/assets", glob: "*" @@ -76,7 +76,13 @@ module Rails end def eager_load_paths - @eager_load_paths ||= paths.eager_load + ActiveSupport::Deprecation.warn "eager_load_paths is deprecated and all autoload_paths are now eagerly loaded." + autoload_paths + end + + def eager_load_paths=(paths) + ActiveSupport::Deprecation.warn "eager_load_paths is deprecated and all autoload_paths are now eagerly loaded." + self.autoload_paths = paths end def autoload_once_paths diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb new file mode 100644 index 0000000000..1081700cd0 --- /dev/null +++ b/railties/lib/rails/engine/railties.rb @@ -0,0 +1,29 @@ +module Rails + class Engine < Railtie + class Railties + include Enumerable + attr_reader :_all + + def initialize + @_all ||= ::Rails::Railtie.subclasses.map(&:instance) + + ::Rails::Engine.subclasses.map(&:instance) + end + + def self.engines + @engines ||= ::Rails::Engine.subclasses.map(&:instance) + end + + def each(*args, &block) + _all.each(*args, &block) + end + + def -(others) + _all - others + end + + delegate :engines, to: "self.class" + end + end +end + +ActiveSupport::Deprecation.deprecate_methods(Rails::Engine::Railties, :engines) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index ca3652c703..99d80b3245 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -20,10 +20,10 @@ module Rails def self.add_shared_options_for(name) class_option :builder, type: :string, aliases: '-b', - desc: "Path to a #{name} builder (can be a filesystem path or URL)" + desc: "Path to some #{name} builder (can be a filesystem path or URL)" class_option :template, type: :string, aliases: '-m', - desc: "Path to an #{name} template (can be a filesystem path or URL)" + desc: "Path to some #{name} template (can be a filesystem path or URL)" class_option :skip_gemfile, type: :boolean, default: false, desc: "Don't create a Gemfile" @@ -61,6 +61,12 @@ module Rails class_option :skip_test_unit, type: :boolean, aliases: '-T', default: false, desc: 'Skip Test::Unit files' + class_option :rc, type: :string, default: false, + desc: "Path to file containing extra configuration options for rails command" + + class_option :no_rc, type: :boolean, default: false, + desc: 'Skip loading of extra configuration options from .railsrc file' + class_option :help, type: :boolean, aliases: '-h', group: :rails, desc: 'Show this help message and quit' end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index e22be40381..b2d1be9b51 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -236,7 +236,7 @@ module Rails end def app_name - @app_name ||= defined_app_const_base? ? defined_app_name : File.basename(destination_root) + @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr(".", "_") end def defined_app_name diff --git a/railties/lib/rails/generators/rails/app/templates/bin/bundle b/railties/lib/rails/generators/rails/app/templates/bin/bundle index e0df7f4440..1123dcf501 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/bundle +++ b/railties/lib/rails/generators/rails/app/templates/bin/bundle @@ -1,3 +1,2 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'rubygems' load Gem.bin_path('bundler', 'bundle') 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 f5d7d698a3..d149413e2e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -22,6 +22,14 @@ module <%= app_const_base %> # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de <% if options.skip_sprockets? -%> # Disable the asset pipeline. 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 bd0a0d44b8..c4cc1162a4 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 @@ -19,9 +19,6 @@ # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log - # Only use best-standards-support built into browsers. - config.action_dispatch.best_standards_support = :builtin - <%- unless options.skip_active_record? -%> # Log the query plan for queries taking more than this (works # with SQLite, MySQL, and PostgreSQL). 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 e813437d75..72281a2fef 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -47,7 +47,7 @@ class <%= controller_class_name %>Controller < ApplicationController # DELETE <%= route_url %>/1 def destroy @<%= orm_instance.destroy %> - redirect_to <%= index_helper %>_url + redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %> end private diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index aacc1be2fc..592e74726e 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -46,7 +46,7 @@ module Rails alias inspect to_s def to_html - (table = '<table>').tap do + '<table>'.tap do |table| properties.each do |(name, value)| table << %(<tr><td class="name">#{CGI.escapeHTML(name.to_s)}</td>) formatted_value = if value.kind_of?(Array) diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index de6795eda2..80ba144441 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -5,13 +5,13 @@ module Rails # paths by a Hash like API. It requires you to give a physical path on initialization. # # root = Root.new "/rails" - # root.add "app/controllers", eager_load: true + # root.add "app/controllers", autoload: true # # The command above creates a new root object and add "app/controllers" as a path. # This means we can get a <tt>Rails::Paths::Path</tt> object back like below: # # path = root["app/controllers"] - # path.eager_load? # => true + # path.autoload? # => true # path.is_a?(Rails::Paths::Path) # => true # # The +Path+ object is simply an enumerable and allows you to easily add extra paths: @@ -30,7 +30,7 @@ module Rails # root["config/routes"].inspect # => ["config/routes.rb"] # # The +add+ method accepts the following options as arguments: - # eager_load, autoload, autoload_once and glob. + # autoload, autoload_once and glob. # # Finally, the +Path+ object also provides a few helpers: # @@ -85,7 +85,8 @@ module Rails end def eager_load - filter_by(:eager_load?) + ActiveSupport::Deprecation.warn "eager_load is deprecated and all autoload_paths are now eagerly loaded." + filter_by(:autoload?) end def autoload_paths @@ -124,9 +125,13 @@ module Rails @glob = options[:glob] options[:autoload_once] ? autoload_once! : skip_autoload_once! - options[:eager_load] ? eager_load! : skip_eager_load! options[:autoload] ? autoload! : skip_autoload! options[:load_path] ? load_path! : skip_load_path! + + if !options.key?(:autoload) && options.key?(:eager_load) + ActiveSupport::Deprecation.warn "the :eager_load option is deprecated and all :autoload paths are now eagerly loaded." + options[:eager_load] ? autoload! : skip_autoload! + end end def children @@ -143,22 +148,37 @@ module Rails expanded.last end - %w(autoload_once eager_load autoload load_path).each do |m| + %w(autoload_once autoload load_path).each do |m| class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{m}! # def eager_load! - @#{m} = true # @eager_load = true + def #{m}! # def autoload! + @#{m} = true # @autoload = true end # end # - def skip_#{m}! # def skip_eager_load! - @#{m} = false # @eager_load = false + def skip_#{m}! # def skip_autoload! + @#{m} = false # @autoload = false end # end # - def #{m}? # def eager_load? - @#{m} # @eager_load + def #{m}? # def autoload? + @#{m} # @autoload end # end RUBY end + def eager_load! + ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." + autoload! + end + + def skip_eager_load! + ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." + skip_autoload! + end + + def eager_load? + ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." + autoload? + end + def each(&block) @paths.each(&block) end diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index 4536fedaa3..3b7f358a5b 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -2,12 +2,12 @@ if RUBY_VERSION < '1.9.3' desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message - Rails 4 requires Ruby 1.9.3+. + Rails 4 prefers to run on Ruby 2.0. You're running #{desc} - Please upgrade to continue. + Please upgrade to Ruby 1.9.3 or newer to continue. end_message end diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake index 6e1334692e..6c3f02eb0c 100644 --- a/railties/lib/rails/tasks/log.rake +++ b/railties/lib/rails/tasks/log.rake @@ -1,9 +1,23 @@ namespace :log do - desc "Truncates all *.log files in log/ to zero bytes" + desc "Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)" task :clear do - FileList["log/*.log"].each do |log_file| - f = File.open(log_file, "w") - f.close + log_files.each do |file| + clear_log_file(file) end end + + def log_files + if ENV['LOGS'] + ENV['LOGS'].split(',') + .map { |file| "log/#{file.strip}.log" } + .select { |file| File.exists?(file) } + else + FileList["log/*.log"] + end + end + + def clear_log_file(file) + f = File.open(file, "w") + f.close + end end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index f0d46fd959..44485d9b14 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -71,6 +71,18 @@ namespace :test do end end + # Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html + desc "Run tests quickly by merging all types and not resetting db" + Rake::TestTask.new(:all) do |t| + t.libs << "test" + t.pattern = "test/**/*_test.rb" + end + + namespace :all do + desc "Run tests quickly, but also reset db" + task :db => %w[db:test:prepare test:all] + end + Rake::TestTask.new(recent: "test:prepare") do |t| since = TEST_CHANGES_SINCE touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + diff --git a/railties/railties.gemspec b/railties/railties.gemspec index e39430560f..a55bf012da 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -27,6 +27,6 @@ Gem::Specification.new do |s| s.add_dependency 'actionpack', version s.add_dependency 'rake', '>= 0.8.7' - s.add_dependency 'thor', '>= 0.15.4', '< 2.0' + s.add_dependency 'thor', '>= 0.17.0', '< 2.0' s.add_dependency 'rdoc', '~> 3.4' end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index f9108ed7b9..7b45623f6c 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -417,17 +417,7 @@ module ApplicationTests require "#{app_path}/config/environment" - assert_equal Time.find_zone!("Wellington"), Time.zone_default - end - - test "timezone can be set on initializers" do - app_file "config/initializers/locale.rb", <<-RUBY - Rails.application.config.time_zone = "Central Time (US & Canada)" - RUBY - - require "#{app_path}/config/environment" - - assert_equal Time.find_zone!("Central Time (US & Canada)"), Time.zone_default + assert_equal "Wellington", Rails.application.config.time_zone end test "raises when an invalid timezone is defined in the config" do @@ -577,6 +567,54 @@ module ApplicationTests assert_equal 'permitted', last_response.body end + test "config.action_controller.action_on_unpermitted_parameters = :raise" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render text: params.require(:post).permit(:name) + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.action_on_unpermitted_parameters = :raise + RUBY + + require "#{app_path}/config/environment" + + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters + + post "/posts", {post: {"title" =>"zomg"}} + assert_match "We're sorry, but something went wrong", last_response.body + end + + test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do + ENV["RAILS_ENV"] = "development" + + require "#{app_path}/config/environment" + + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters is :log by defaul on test" do + ENV["RAILS_ENV"] = "test" + + require "#{app_path}/config/environment" + + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do + ENV["RAILS_ENV"] = "production" + + require "#{app_path}/config/environment" + + assert_equal false, ActionController::Parameters.action_on_unpermitted_parameters + end + test "config.action_dispatch.ignore_accept_header" do make_basic_app do |app| app.config.action_dispatch.ignore_accept_header = true diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb index 31811e7f92..595f58bd91 100644 --- a/railties/test/application/initializers/load_path_test.rb +++ b/railties/test/application/initializers/load_path_test.rb @@ -64,7 +64,7 @@ module ApplicationTests add_to_config <<-RUBY config.root = "#{app_path}" - config.eager_load_paths << "#{app_path}/lib" + config.autoload_paths << "#{app_path}/lib" RUBY require "#{app_path}/config/environment" diff --git a/railties/test/application/middleware/best_practices_test.rb b/railties/test/application/middleware/best_practices_test.rb deleted file mode 100644 index f6783c6ca2..0000000000 --- a/railties/test/application/middleware/best_practices_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'isolation/abstract_unit' - -module ApplicationTests - class BestPracticesTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - def setup - build_app - boot_rails - require 'rack/test' - extend Rack::Test::Methods - simple_controller - end - - def teardown - teardown_app - end - - test "simple controller in production mode returns best standards" do - get '/foo' - assert_equal "IE=Edge,chrome=1", last_response.headers["X-UA-Compatible"] - end - - test "simple controller in development mode leaves out Chrome" do - app("development") - get "/foo" - assert_equal "IE=Edge", last_response.headers["X-UA-Compatible"] - end - end -end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index c5a0d02fe1..73ef2046c0 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -45,8 +45,7 @@ module ApplicationTests "ActionDispatch::ParamsParser", "Rack::Head", "Rack::ConditionalGet", - "Rack::ETag", - "ActionDispatch::BestStandardsSupport" + "Rack::ETag" ], middleware end diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 4029984ce9..f6a77a0d17 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -54,11 +54,11 @@ module ApplicationTests assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first end - test "booting up Rails yields a list of paths that are eager" do - eager_load = @paths.eager_load - assert eager_load.include?(root("app/controllers")) - assert eager_load.include?(root("app/helpers")) - assert eager_load.include?(root("app/models")) + test "booting up Rails yields a list of paths that will be eager loaded" do + autoload_paths = @paths.autoload_paths + assert autoload_paths.include?(root("app/controllers")) + assert autoload_paths.include?(root("app/helpers")) + assert autoload_paths.include?(root("app/models")) end test "environments has a glob equal to the current environment" do diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index f65b5e2f2d..6595c40f8b 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -37,27 +37,27 @@ module ApplicationTests end def test_should_run_file - app_file "script/count_users.rb", <<-SCRIPT + app_file "bin/count_users.rb", <<-SCRIPT puts User.count SCRIPT - assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "script/count_users.rb"` } + assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "bin/count_users.rb"` } end def test_should_set_dollar_0_to_file - app_file "script/dollar0.rb", <<-SCRIPT + app_file "bin/dollar0.rb", <<-SCRIPT puts $0 SCRIPT - assert_match "script/dollar0.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/dollar0.rb"` } + assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bundle exec rails runner "bin/dollar0.rb"` } end def test_should_set_dollar_program_name_to_file - app_file "script/program_name.rb", <<-SCRIPT + app_file "bin/program_name.rb", <<-SCRIPT puts $PROGRAM_NAME SCRIPT - assert_match "script/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/program_name.rb"` } + assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "bin/program_name.rb"` } end def test_with_hook diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index ee93dc49cd..83f43d1025 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -70,7 +70,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_application_new_exits_with_non_zero_code_on_invalid_application_name - quietly { system 'rails new test' } + quietly { system 'rails new test --no-rc' } assert_equal false, $?.success? end @@ -164,6 +164,11 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_config_database_app_name_with_period + run_generator [File.join(destination_root, "common.usage.com"), "-d", "postgresql"] + assert_file "common.usage.com/config/database.yml", /common_usage_com/ + end + def test_config_postgresql_database run_generator([destination_root, "-d", "postgresql"]) assert_file "config/database.yml", /postgresql/ diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index c34ce285e3..013cb78252 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -39,6 +39,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_instance_method :destroy, content do |m| assert_match(/@user\.destroy/, m) + assert_match(/User was successfully destroyed/, m) end assert_instance_method :set_user, content do |m| diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 7ae1b5ccfd..68d96bae94 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -279,7 +279,7 @@ Module.new do environment = File.expand_path('../../../../load_paths', __FILE__) require_environment = "-r #{environment}" - `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path} --skip-gemfile` + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path} --skip-gemfile --no-rc` File.open("#{app_template_path}/config/boot.rb", 'w') do |f| f.puts "require '#{environment}'" f.puts "require 'rails/all'" diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 12f18b9dbf..6f860469fd 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -135,56 +135,48 @@ class PathsTest < ActiveSupport::TestCase assert_equal 2, @root.autoload_once.size end - test "it is possible to mark a path as eager loaded" do + test "marking a path as eager loaded is deprecated" do @root["app"] = "/app" - @root["app"].eager_load! - assert @root["app"].eager_load? - assert @root.eager_load.include?(@root["app"].to_a.first) + assert_deprecated{ @root["app"].eager_load! } + assert_deprecated{ assert @root["app"].eager_load? } + assert_deprecated{ assert @root.eager_load.include?(@root["app"].to_a.first) } end - test "it is possible to skip a path from eager loading" do + test "skipping a path from eager loading is deprecated" do @root["app"] = "/app" - @root["app"].eager_load! - assert @root["app"].eager_load? + assert_deprecated{ @root["app"].eager_load! } + assert_deprecated{ assert @root["app"].eager_load? } - @root["app"].skip_eager_load! - assert !@root["app"].eager_load? - assert !@root.eager_load.include?(@root["app"].to_a.first) + assert_deprecated{ @root["app"].skip_eager_load! } + assert_deprecated{ assert !@root["app"].eager_load? } + assert_deprecated{ assert !@root.eager_load.include?(@root["app"].to_a.first) } end - test "it is possible to add a path without assignment and mark it as eager" do - @root.add "app", with: "/app", eager_load: true - assert @root["app"].eager_load? - assert @root.eager_load.include?("/app") + test "adding a path with eager_load option is deprecated" do + assert_deprecated{ @root.add "app", with: "/app", eager_load: true } + assert_deprecated{ assert @root["app"].eager_load? } + assert_deprecated{ assert @root.eager_load.include?("/app") } end - test "it is possible to add multiple paths without assignment and mark them as eager" do - @root.add "app", with: ["/app", "/app2"], eager_load: true - assert @root["app"].eager_load? - assert @root.eager_load.include?("/app") - assert @root.eager_load.include?("/app2") - end - - test "it is possible to create a path without assignment and mark it both as eager and load once" do - @root.add "app", with: "/app", eager_load: true, autoload_once: true - assert @root["app"].eager_load? - assert @root["app"].autoload_once? - assert @root.eager_load.include?("/app") - assert @root.autoload_once.include?("/app") + test "adding multiple paths with eager_load option is deprecated" do + assert_deprecated{ @root.add "app", with: ["/app", "/app2"], eager_load: true } + assert_deprecated{ assert @root["app"].eager_load? } + assert_deprecated{ assert @root.eager_load.include?("/app") } + assert_deprecated{ assert @root.eager_load.include?("/app2") } end test "making a path eager more than once only includes it once in @root.eager_paths" do @root["app"] = "/app" - @root["app"].eager_load! - @root["app"].eager_load! - assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size + assert_deprecated{ @root["app"].eager_load! } + assert_deprecated{ @root["app"].eager_load! } + assert_deprecated{ assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size } end test "paths added to a eager_load path should be added to the eager_load collection" do @root["app"] = "/app" - @root["app"].eager_load! + assert_deprecated{ @root["app"].eager_load! } @root["app"] << "/app2" - assert_equal 2, @root.eager_load.size + assert_deprecated{ assert_equal 2, @root.eager_load.size } end test "it should be possible to add a path's default glob" do diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index a4a75fe459..37d0be107c 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -1241,6 +1241,12 @@ YAML assert_equal '/foo/bukkits/bukkit', last_response.body end + test "engines method is properly deprecated" do + boot_rails + + assert_deprecated { app.railties.engines } + end + private def app Rails.application diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index a1c52f01dc..80559a6e36 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -89,6 +89,7 @@ module ApplicationTests get '/generate_application_route', to: 'posts#generate_application_route' get '/application_route_in_view', to: 'posts#application_route_in_view' get '/engine_polymorphic_path', to: 'posts#engine_polymorphic_path' + get '/engine_asset_path', to: 'posts#engine_asset_path' end RUBY @@ -113,6 +114,10 @@ module ApplicationTests def engine_polymorphic_path render text: polymorphic_path(Post.new) end + + def engine_asset_path + render inline: "<%= asset_path 'images/foo.png' %>" + end end end RUBY @@ -211,6 +216,10 @@ module ApplicationTests # and in an application get "/application_polymorphic_path" assert_equal "/posts/44", last_response.body + + # test that asset path will not get script_name when generated in the engine + get "/someone/blog/engine_asset_path" + assert_equal "/images/foo.png", last_response.body end test "route path for controller action when engine is mounted at root" do |